Repository: leon-thomm/Ryven (language: python)

Issue:
Ryvencore upgrades
Continuing work started in #137
publish-package.yml: Update GitHub Actions



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 43752021..669261c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,12 +4,12 @@
 __pycache__/
 venv/
 temp/
-tests/
 code_gen/
 __own__/
 build/
 dist/
 *.egg-info
+tests/data
 
 ryvencore-drawio.xml
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 25e119e2..7979d24b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,14 +11,15 @@ There are many ways you can contribute to the Ryven project. Particularly effect
 - docstrings
 - online documentation
 - example content: nodes and projects
-- tutorials
-
-Also check out the other repositories.
+- modularity improvements
 
 # Creating Good Issues :mega:
 
-- you might want to check the [information on the website](https://ryven.org/guide)
-- be precise and include a minimal procedure to reproduce the problem
-- notice there's a *discussions* section which can be used for all kinds of questions or suggestions
+- you might want to check
+  - the [information on the website](https://ryven.org/)
+  - the [wiki](https://github.com/leon-thomm/Ryven/wiki)
+  - the [ryvencore documentation](https://leon-thomm.github.io/ryvencore/)
+- be precise and **include a minimal procedure to reproduce the problem**
+- notice there's also a [discussions section](https://github.com/leon-thomm/ryven/discussions), but you can also use the issue tracker for questions
 
 Thanks for hanging around and have a nice day!
diff --git a/CREDITS.md b/CREDITS.md
new file mode 100644
index 00000000..34e3a3e5
--- /dev/null
+++ b/CREDITS.md
@@ -0,0 +1,28 @@
+*This is the list of the Ryven project's significant contributors. It does not list everyone who has contributed but only those who have substantially impacted the project. To see the full list of contributors, look at the revision history in source control. To request change you may contact one of the maintainers directly.*
+
+**Advisors**
+
+- [dumblob](https://github.com/dumblob)
+- [Moritz Mussgnug](https://www.linkedin.com/in/moritz-mussgnug-053b7b198/)
+- [Yannick Reich](https://github.com/yannick-reich)
+
+_Code_
+
+- [Jan Janssen](https://github.com/jan-janssen)
+    - conda port
+- [Marcin Morys](https://github.com/mmorys)
+    - pip packaging
+- [sphh](https://github.com/sphh)
+    - command line arguments
+- [Tanneguy de Villemagne](https://github.com/Tanneguydv)
+    - CAD integration
+
+_Design_
+
+- [Samuel Wölfl](https://github.com/samuelwoelfl)
+    - incredible UI design for Ryven 3
+    - a lot of design work for the websites
+
+**Special Thanks**
+
+- [ETH Student Project House](https://sph.ethz.ch) and everyone involved
diff --git a/LICENSE b/LICENSE
index e0594380..f1e02a41 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 Leon Thomm
+Copyright (c) 2022 Leon Thomm
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index b6e799d1..ea0b2ee8 100644
--- a/README.md
+++ b/README.md
@@ -1,103 +1,165 @@
+> This project is not very actively maintained. With the latest release - which I'm currently working on getting mostly stable - the project became quite accurately what I wanted it to be, and I'll have limited time for it in the future. If you are a contributor and interested in maintaining, please [contact me](mailto:l.thomm@mailbox.org).
+
 <p align="center">
   <img src="./docs/img/logo.png" alt="drawing" width="70%"/>
 </p>
 
-# A simple visual node editor for Python
+Ryven is an experimental node editor written in Python, it implements a Qt-based visual interface for flow-based programming in Python. It provides a powerful system for developing nodes executing any Python code, and an editor for building graphs using those nodes. Ryven features a bunch of configuration options and a headless mode for running graphs without any GUI. Some relevant GitHub repos:
 
-**Ryven combines flow-based visual scripting with Python. It gives you absolute freedom for your nodes and a simple system for programming them. While there are some example node packages, you will most likely rely mostly on your own nodes.**
+* [ryvencore](https://github.com/leon-thomm/ryvencore): backend / core framework
+* [ryvencore-qt](https://github.com/leon-thomm/ryvencore-qt): Qt frontend classes for ryvencore
+* [ryven-blender](https://github.com/leon-thomm/ryven-blender), [ryven-unreal](https://github.com/leon-thomm/ryven-unreal): Ryven plugins for Blender and UE4
+  * _not actively maintained_
+* [PythonOCC nodes for Ryven](https://github.com/Tanneguydv/Pythonocc-nodes-for-Ryven): WIP Ryven nodes for PythonOCC (3D CAD)
+* [ironflow](https://github.com/pyiron/ironflow): WIP node interface in jupyter for [pyiron](https://github.com/pyiron) based on ryvencore
 
-![](./docs/img/themes_with_logo.png)
+## Installation and Configuration
 
-<!-- Ryven is based on [ryvencore](https://github.com/leon-thomm/ryvencore) and [ryvencore-qt](https://github.com/leon-thomm/ryvencore-qt). A detailed guide for Ryven can be found [here](https://ryven.org/guides.html#/). -->
+Once you have Python and pip installed, Ryven is available on PyPI via
 
-While Ryven has a large Qt-based frontend which affects performance of flow execution, you can disable particularly expensive features, and a project made in the editor can be deployed directly on the backend (ryvencore) via RyvenConsole, which does not have a single dependency!
+```
+pip install ryven
+```
 
-| Ryven repos on GitHub | -------------------------------------------------------------------------------- |
-|---|---|
-| [ryvencore](https://github.com/leon-thomm/ryvencore) | backend / core framework |
-| [ryvencore-qt](https://github.com/leon-thomm/ryvencore-qt) | Qt frontend |
-| [ryven-blender](https://github.com/leon-thomm/ryven-blender) | Ryven plugin for Blender |
-| [ryven-unreal](https://github.com/leon-thomm/ryven-unreal) | Ryven plugin for Unreal Engine |
-| [ryven-website](https://github.com/leon-thomm/ryven-website) | Ryven website sources |
-| [PythonOCC nodes for Ryven](https://github.com/Tanneguydv/Pythonocc-nodes-for-Ryven) | Ryven nodes for PythonOCC |
+There is also a [conda-forge package](https://anaconda.org/conda-forge/ryven) (`conda install -c conda-forge ryven`).
 
-To get started with Ryven, these are the resources that guide you through the process (in an order that makes the most sense):
-1. the quick start guide below
-2. the tutorials in the `docs/node_tutorials` directory
-3. a longer [guide on the website](https://ryven.org/guide#/) for details
+Ryven can be launched from the command line with `ryven`. If you installed Ryven into a Python virtual environment, the environment needs to be activated first.
 
-Ryven comes with some example nodes, but these are, indeed, just examples, and there's no guarantee that all of them will stay. I would like to open a repository for maintaining particularly useful (frameworks of) nodes, but I will need contributors for that.
+Ryven itself only comes with some small example nodes. You should use Ryven either to develop nodes, or use a third-party nodes package for your use case if there is one. The example nodes are - indeed - just examples, and not stable in any way, so you should not depend on them.
 
-### Installation
+When installed, ryven will create a directory `.ryven` in your user home with the following structure:
 
 ```
-pip install ryven
+~/.ryven
+├── nodes
+│   ├── your_nodes_pkg_1
+│       ├── nodes.py
+│       └── gui.py
+│   └── ...
+├── saves
+│   ├── your_project_1.json
+│   └── ...
+└── ryven.cfg
 ```
 
-and now you can launch it by running `ryven` on your terminal, and `ryven_console` for RyvenConsole. If you want to start Ryven from a Python script, just import it and run it like this
-
-```python
-import ryven
+The `ryven.cfg` file contains global configurations for Ryven.
+
+Ryven can be configured in four ways:
+1. from the command line, e.g. `ryven --nodes your_nodes_pkg_1 --no-animations`
+2. from a configuration file, e.g. in `~/.ryven/ryven.cfg`:
+   > ```
+   > nodes = your_nodes_pkg_1
+   > no_animations = True
+   > ``` 
+3. through arguments when it's integrated in another Python application, e.g.
+   > ```python
+   > import ryven
+   > ryven.run_ryven(nodes=['your_nodes_pkg_1'], no_animations=True)
+   > ```
+4. using a GUI in the startup dialog
+   * you can also convert the manual configuration to cmd line args (or a config file) in the dialog
+
+See `ryven --help` for a list of available options.
+
+To deploy a Ryven project headless, without any GUI, use the `ryven-console` command.
+
+<details>
+<summary>Example: headless deployment with REPL access</summary>
+
+```bash
+> ryven-console /home/leon/.ryven/saves/basics.json
+Welcome to the Ryven Console! Your project has been loaded.
+You can access the ryvencore session by typing `session`.
+For more information, visit https://leon-thomm.github.io/ryvencore/
+
+>>> f = session.flows[0]
+>>> ctr_var_result_node = f.nodes[2]
+>>> ctr_set_var_node = f.nodes[8]
+>>> ctr_var_result_node.val
+3738
+>>> ctr_set_var_node.update(0)
+>>> ctr_var_result_node.val
+3739
+```
 
-ryven.run_ryven(
-    # args...
-)
+</details>
 
-```
+## Editor Usage
 
-### quick start
+Quickstart guide:
 
-A super quick intro to Ryven.
+* open Ryven by running `ryven` from the command line
+* you should see the startup dialog
+* create a new project
+* import some example nodes
+  * `File -> Import Example Nodes` and select `<installation_dir>/example_nodes/std/nodes.py`
+* you should now see a list of nodes on the left
+* drag and drop them into the scene and connect them with your mouse
+* everything is being executed at runtime; try this:
+  * drag two `val` nodes into the scene
+  * wire them together with a `+` node
+  * display the result in a `result` node 
+  * now replace one of them with a slider node generating real numbers
+* by right-clicking, you can also get an interactive nodes list preview inside the scene
+* you can pan around also with the right mouse button (hold and drag)
+* and zoom via `ctrl + scroll`
 
-**editor usage**
+## Developing Nodes
 
-Open Ryven by typing `ryven` in your terminal (or running `Ryven.py` with python), and create a new project. Import some example nodes via `File -> Import Example Nodes` and select `std/nodes.py`. You should now see a long list of nodes on the left. Drag and drop them into the scene and get a feeling for how they work, everything is being executed at realtime. For instance, drag two `val` nodes into the scene, wire them together with a `+` node and display the result in a `result` node. Now replace one of them with a slider node generating real numbers. You can also get an interactive nodes list preview inside the scene by right-clicking. You can pan around also with the right mouse button, and zoom via `ctrl + scroll`.  You can also create new scripts, rename and delete them.
+<details>
+<summary>quick start into to developing node packages</summary>
 
-Now let's check out the small example projects: open a new Ryven window and load one of them. Take a closer look and understand what they do.
+Navigate to `~/.ryven/nodes/` and create a sub-directory of the following structure
 
-At this point you are ready to start building your own nodes.
+```
+~/.ryven/nodes
+└── your_nodes_pkg_1
+    ├── nodes.py
+    └── gui.py
+```
 
-**defining nodes**
+With the following contents:
 
-Navigate to the `~/.ryven/packages/` directory and create a new folder `<your_package_name>`. Inside this folder create a python file `nodes.py` and fill it with the following content:
+`nodes.py`
 
 ```python
-from ryven.NENV import *
+from ryven.node_env import *
 
 # your node definitions go here
 
-export_nodes(
-    # list your node classes here, as tuple
-)
+export_nodes([
+    # list your node classes here
+])
 ```
 
-and now you can define your own node classes. Reference the ones you want to expose to Ryven in the `export_nodes` function (for example `export_nodes(MyNode, )` or `export_nodes(Node1, Node2, )`). Let's define two basic nodes:
+`gui.py`
+
+```python
+from ryven.gui_env import *
+
+# your node gui definitions go here
+
+export_guis([
+    # list your node gui classes here
+])
+```
 
-one which generates random numbers
+You can now start defining your own nodes. Let's define two basic nodes. One which generates random numbers
 
 ```python
 from random import random
 
 class RandNode(Node):
     """Generates scaled random float values"""
-    # the docstring will be shown as tooltip in the editor
 
-    title = 'Rand'  # the display_title is title by default
-    tags = ['random', 'numbers']  # for better search
-    
-    init_inputs = [  # one input
-        NodeInputBP(dtype=dtypes.Data(default=1))
-        # the dtype will automatically provide a suitable widget
-    ]
-    init_outputs = [  # and one output
-        NodeOutputBP()
-    ]
-    color = '#fcba03'
+    title = 'Rand'
+    tags = ['random', 'numbers']
+    init_inputs = [NodeInputType()]
+    init_outputs = [NodeOutputType()]
 
     def update_event(self, inp=-1):
-        # update first output
         self.set_output_val(0, 
-            random() * self.input(0)  # random float between 0 and value at input
+            Data(random() * self.input(0).payload)
         )
 ```
 
@@ -106,125 +168,195 @@ and another one which prints them
 ```python
 class PrintNode(Node):
     title = 'Print'
-    init_inputs = [
-        NodeInputBP(),
-    ]
-    color = '#A9D5EF'
+    init_inputs = [NodeInputType()]
 
     def update_event(self, inp=-1):
         print(self.input(0))
 ```
 
-and that's it! Go ahead and import your nodes package in Ryven. Place both in the scene and connect the `Rand` node to your `Print` node.
+and expose them to Ryven
+
+```python
+export_nodes([
+    RandNode,
+    PrintNode,
+])
+```
 
-***
+That's it! You can import your nodes package in Ryven (`File -> Import Nodes`), place the nodes in the graph, and wire them up. Now add a `val` node and connect it to the `Rand` node, to feed its input with data. If you type a number into the widget of the `val` node and hit enter, it will send the number to the `Rand` node, which will send a scaled random number to the `Print` node, which will print it to the standard output.
 
-You can do a lot more than shown above. A summary of the main features:
+Notice that the standard output is by default the in-editor console, which you can access at the very bottom of the editor window (drag the blue handle up to make it visible).
 
-- **many modifiable themes**, including light themes
-- **simple and unrestricted nodes system**
-- **actions / right-click operations system for nodes**
-- **variables system** with update mechanism for nodes that automatically adapt to change of data
-- **logging support**
-- **rendering flow images**
-- **stylus support** for adding handwritten notes / drawings on touch devices
-- **exec flow support** like [UnrealEngine BluePrints](https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/Blueprints/)
-- **Qt widgets support**
+### Adding GUI
 
-and some examples for those:
+You can now spice up your nodes with some GUI. Ryven runs on Qt, using the [qtpy](https://github.com/spyder-ide/qtpy) library. You can configure the GUI of your nodes in a separate `gui.py` file, and add custom Qt widgets to your nodes. Make sure to always clearly separate the node logic from the GUI. The `nodes.py` file should NOT have any dependency to Qt. One of the central features of Ryven is to run projects headless (on ryvencore) without any GUI dependencies, if your node packages obey the rules.
+
+Let's give them some color and add a slider to the `Rand` node, in `gui.py`:
 
-#### actions / right-click operations system for nodes
-which can be edited through the API at any time.
 ```python
-class MyNode(Node):
-    ...
+from ryven.gui_env import *
 
-    def a_method(self):
-        self.actions['do something'] = {
-            'method': self.do_sth,
-        }
+from qtpy.QtWidgets import QSlider
+from qtpy.QtCore import Qt
 
-    # with some method...
-    def do_sth(self):
-        ...
+
+class RandSliderWidget(NodeInputWidget, QSlider):
+    """a standard Qt slider widget, which updates the node
+    input it is attached to, every time the slider value changes"""
+    
+    def __init__(self, params):
+        NodeInputWidget.__init__(self, params)
+        QSlider.__init__(self)
+        
+        self.setOrientation(Qt.Horizontal)
+        self.setMinimumWidth(100)
+        self.setMinimum(0)
+        self.setMaximum(100)
+        self.setValue(50)
+        self.valueChanged.connect(self.value_changed)
+    
+    def value_changed(self, val):
+        # updates the node input this widget is attached to
+        self.update_node_input(Data(val))
+    
+    def get_state(self) -> dict:
+        # return the state of the widget
+        return {'value': self.value()}
+    
+    def set_state(self, state: dict):
+        # set the state of the widget
+        self.setValue(state['value'])
+    
+
+class RandNodeGui(NodeGUI):
+    color = '#fcba03'
+    
+    # register the input widget class
+    input_widget_classes = { 'slider': RandSliderWidget }
+    
+    # attach the slider widget to the first node input
+    # display it _below_ the input pin
+    init_input_widgets = {
+        0: {'name': 'slider', 'pos': 'below'}
+    }
+
+export_guis([
+    RandNodeGui,
+])
 ```
 
-#### Qt widgets
-You can add custom Qt widgets for your nodes. Define a `widgets.py` file next to your `nodes.py` with similar structure to `nodes.py`, see the guide for detailed instructions.
+and you now just need to reference the `RandNodeGUI` in `nodes.py`:
 
-`widgets.py`
 ```python
-from ryven.NWENV import *
-from qtpy.QtWidgets import QWidget
+guis = import_guis(__file__)
 
-class SomeMainWidget(MWB, QWidget):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QWidget.__init__(self)
+class RandNode(Node):
     ...
+    GUI = guis.RandNodeGui
+```
 
-class SomeInputWidget(IWB, QWidget):
-    def __init__(self, params):
-        IWB.__init__(self, params)
-        QWidget.__init__(self)
-    ...
+The value provided by an input widget (through `self.update_node_input(val)`) will be returned in `Node` by `self.input(0)` only when the corresponding input is _not_ connected. Otherwise the value of the connected output will be returned.
+
+So now we can reconstruct the previous example, but we don't need to connect the `val` node to the `Rand` node anymore. Change the slider and see how many different random values are printed.
+
+</details>
+
+Please find further resources on the GitHub wiki page in this repository.
+
+## Features
+
+A (possibly incomplete) list of features:
+
+- **headless mode** to run projects without GUI dependencies at high performance
+- **sophisticated nodes system** allowing for stateful nodes and widgets
+- **cross-platform**; running anywhere where Qt runs (for GUI), or simply Python (headless)
+- **rendering flow images** into PNGs
+- built-in **exec flow support** (like [UE BluePrints](https://docs.unrealengine.com/5.0/en-US/blueprints-visual-scripting-in-unreal-engine/)) unlike most other node editors
+- **custom Qt widgets support**
+- various **themes**, including light
+- **right-click operations system for nodes**
+- **variables add-on** with observer mechanism, to build nodes that automatically adapt to change of data
+- very basic **logging support**
+- primitive, very experimental **stylus support** for adding handwritten notes on touch devices
+
+<!--
+
+### Nodes API in action
+
+<table>
+<tr>
+<th>stateful nodes</th>
+<th>exec nodes</th>
+<th>custom Qt widgets</th>
+<th>variables</th>
+<th>logging</th>
+</tr>
+<tr>
+
+<td>
+<sub>
+
+is this text small, yes it is :>>>
 
-export_widgets(
-    SomeMainWidget,
-    SomeInputWidget,
-)
-```
-`nodes.py`
 ```python
-class MyNode(Node):
-    main_widget_class = MyNode_MainWidget
-    main_widget_pos = 'below ports'  # alternatively 'between ports'
+this is a test
 ```
 
-#### stylus support
-<p align="center">
-  <img src="./docs/img/stylus_dark.png" alt="drawing" width="70%"/>
-</p>
+</sub>
+</td>
+
+<td>
+<sub>
 
-#### logging support
 ```python
-import logging
+asdf
+```
 
-class MyNode(Node):
-    def somewhere(self):
-        self.logger = self.new_logger('nice log')
-    
-    def update_event(self, inp=-1):
-        self.logger.info('updated!')
+</sub>
+</td>
+
+<td>
+<sub>
+
+```python
+qewr
 ```
 
-#### variables system
-with an update mechanism to build nodes that automatically adapt to change of data.
+</sub>
+</td>
+
+<td>
+<sub>
 
 ```python
-class MyNode(Node):
-    def a_method(self):
-        self.register_var_receiver('x', method=self.process)
+this is a test
+```
 
-    # with some method...
-    def process(self, val_of_x):
-        # processing new value of var 'x'
-        ...
+</sub>
+</td>
+
+<td>
+<sub>
+
+```python
+this is a test
 ```
 
-## Contributions
+</sub>
+</td>
+
+</tr>
+</table>
 
-Contributing guidelines: [here](https://github.com/leon-thomm/Ryven/blob/dev/CONTRIBUTING.md).
+-->
 
-Particularly effective ways to contribute outside direct development of the software include
+## License
 
-- creating examples
-- creating tutorials
-- creating node packages
-- improving documentation
+* This repository is licensed under the MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
+* The underlying library ryvencore is licensed under LGPL-2.1 (LICENSE-LGPL-2.1 or https://www.gnu.org/licenses/lgpl-2.1.html)
 
-Also notice that there's a *discussions* area in this repo.
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be licensed as above, without any additional terms or conditions.
 
-The docs page on the website is made with [Docsify](https://github.com/docsifyjs/docsify/), so you can improve it by simply editing the markdown. The whole [website sources](https://github.com/leon-thomm/ryven-website) are also on GitHub.
+## Credits
 
-Cheers.
+A huge thanks to the contributors. This project does not exist without the open-source community. I personally want to particularly thank the people listed in the CREDITS.md file, which have strongly impacted the project.
\ No newline at end of file
diff --git a/docs/node tutorials/README.md b/docs/node tutorials/README.md
index 2aa7b021..d668868a 100644
--- a/docs/node tutorials/README.md
+++ b/docs/node tutorials/README.md
@@ -1,1 +1,3 @@
+**TODO: OUTDATED**
+
 This directory hosts some tutorials for nodes built for Ryven to make it easier to get started. Not everything you see here might be 100% up to date with the most recent version. Feel free to extend.
\ No newline at end of file
diff --git a/docs/node tutorials/basic operators.md b/docs/node tutorials/basic operators.md
index 8823fb16..68a7c6ac 100644
--- a/docs/node tutorials/basic operators.md
+++ b/docs/node tutorials/basic operators.md
@@ -3,23 +3,23 @@ This tutorial shows how to create nodes for some basic operations/relations, in
 As always, we should first define a parent node class for this scope. It can be completely empty, but it's good practice to always include it as this prevents node identification issues which might occur in complex hierarchies in case you add it later.
 
 ```python
-from ryven.NENV import *
+from ryven.node_env import *
 
-class NodeBase(Node):
 
+class NodeBase(Node):
     version = 'v0.1'
-    
+
     # most of our operators are binary, so let's already put this here (we can override that in subclasses for the exceptions)
     init_inputs = [
         NodeInputBP(dtype=dtypes.Data(size='s')),
         NodeInputBP(dtype=dtypes.Data(size='s')),
     ]
     # operators in python can be defined on objects of different classes, so let's just use Data dtype inputs for now. recall that Data inputs will evaluate input expressions (as opposed to just interpreting it as string for example)
-    
+
     init_outputs = [
         NodeOutputBP(),
     ]
-    
+
     style = 'small'
 ```
 
diff --git a/docs/node tutorials/doc_examples/custom_widgets/nodes.py b/docs/node tutorials/doc_examples/custom_widgets/nodes.py
index b1398338..5cf8602d 100644
--- a/docs/node tutorials/doc_examples/custom_widgets/nodes.py
+++ b/docs/node tutorials/doc_examples/custom_widgets/nodes.py
@@ -1,5 +1,5 @@
-from ryven.NENV import *
-widgets = import_widgets(__file__)  # this loads all exported widgets from widgets.py into the object
+from ryven.node_env import *
+widgets = import_guis(__file__)  # this loads all exported widgets from gui.py into the object
 
 
 class MyNode(Node):
diff --git a/docs/node tutorials/doc_examples/custom_widgets/widgets.py b/docs/node tutorials/doc_examples/custom_widgets/widgets.py
index 5baabd79..430700c1 100644
--- a/docs/node tutorials/doc_examples/custom_widgets/widgets.py
+++ b/docs/node tutorials/doc_examples/custom_widgets/widgets.py
@@ -1,4 +1,4 @@
-from ryven.NWENV import *
+from ryven.gui_env import *
 
 from qtpy.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit
 
diff --git a/docs/node tutorials/doc_examples/mypackage/nodes.py b/docs/node tutorials/doc_examples/mypackage/nodes.py
index 4b2b53f3..2f4bb392 100644
--- a/docs/node tutorials/doc_examples/mypackage/nodes.py
+++ b/docs/node tutorials/doc_examples/mypackage/nodes.py
@@ -1,4 +1,4 @@
-from ryven.NENV import *
+from ryven.node_env import *
 
 
 class NodeBase(Node):
diff --git a/reinstall.bat b/rebuild.sh
old mode 100644
new mode 100755
similarity index 52%
rename from reinstall.bat
rename to rebuild.sh
index a3ad96f7..befd60b0
--- a/reinstall.bat
+++ b/rebuild.sh
@@ -1,4 +1,4 @@
 pip uninstall --yes ryven
-rmdir /s /q build
-rmdir /s /q dist
+rm -r build
+rm -r dist
 pip install .
\ No newline at end of file
diff --git a/recreate_uic_files.bat b/recreate_uic_files.bat
deleted file mode 100644
index 7effc8a5..00000000
--- a/recreate_uic_files.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-pyside2-uic ryven/gui/uic/main_window.ui > ryven/gui/uic/ui_main_window.py
-pyside2-uic ryven/gui/uic/script.ui > ryven/gui/uic/ui_script.py
\ No newline at end of file
diff --git a/recreate_uic_files.sh b/recreate_uic_files.sh
new file mode 100755
index 00000000..93f6ee7c
--- /dev/null
+++ b/recreate_uic_files.sh
@@ -0,0 +1,19 @@
+# for all *.ui files in ryven/gui/uic
+for file in ryven/gui/uic/*.ui; do
+    # get filename without extension
+    filename=$(basename -- "$file")
+    filename="${filename%.*}"
+
+    echo "Processing $file to ui_$filename.py"
+
+    # if an argument --pyside6 is given, use pyside6-uic instead of pyside2-uic
+    if [ "$1" = "--pyside6" ]; then
+        pyside6-uic "$file" > "ryven/gui/uic/ui_$filename.py"
+    else
+        pyside2-uic "$file" > "ryven/gui/uic/ui_$filename.py"
+    fi
+
+    # replace pyside imports with qtpy
+    sed -i 's/from PySide6/from qtpy/g' "ryven/gui/uic/ui_$filename.py"
+    sed -i 's/from PySide2/from qtpy/g' "ryven/gui/uic/ui_$filename.py"
+done
\ No newline at end of file
diff --git a/ryven/NENV.py b/ryven/NENV.py
deleted file mode 100644
index 0a6177da..00000000
--- a/ryven/NENV.py
+++ /dev/null
@@ -1,130 +0,0 @@
-"""This module automatically imports all requirements for custom nodes.
-It should lie in the same location as Ryven.py so it can get imported directly from the custom sources
-without path modifications which caused issues in the past."""
-
-import inspect
-import sys
-import os
-
-
-# types
-Node = None
-NodeInputBP = None
-NodeOutputBP = None
-dtypes = None
-
-
-def init_node_env():
-    global Node
-    global NodeInputBP
-    global NodeOutputBP
-    global dtypes
-
-
-    if os.environ['RYVEN_MODE'] == 'gui':
-
-        from ryvencore_qt import \
-            NodeInputBP as NodeInputBP_, \
-            NodeOutputBP as NodeOutputBP_, \
-            dtypes as dtypes_
-        from ryven.main.nodes.NodeBase import NodeBase as Node_
-
-        Node = Node_
-        NodeInputBP = NodeInputBP_
-        NodeOutputBP = NodeOutputBP_
-        dtypes = dtypes_
-
-    else:
-
-        # import sources directly from backend if not running in gui mode
-        from ryvencore import \
-            Node as _Node, \
-            NodeInputBP as NodeInputBP_, \
-            NodeOutputBP as NodeOutputBP_, \
-            dtypes as dtypes_
-
-        class NodeWrp(_Node):
-            """
-            Wraps the nodes s.t. their usages of ryvencore-qt or Ryven features don't brake them.
-            TODO: move actions to ryvencore
-            """
-
-            def __init__(self, params):
-                self.actions = dict()
-                super().__init__(params)
-
-        Node = NodeWrp
-        NodeInputBP = NodeInputBP_
-        NodeOutputBP = NodeOutputBP_
-        dtypes = dtypes_
-
-
-from ryven.main.utils import load_from_file
-
-
-def import_widgets(origin_file: str, rel_file_path='widgets.py'):
-    """
-    Import all exported widgets from 'widgets.py' with respect to the origin_file location.
-    Returns an object with all exported widgets as attributes for direct access.
-    """
-
-    caller_location = os.path.dirname(origin_file)
-
-    # alternative solution without __file__ argument; does not work with debugging, so it's not the best idea
-    #   caller_location = os.path.dirname(stack()[1].filename)  # getting caller file path from stack frame
-
-    abs_path = os.path.join(caller_location, rel_file_path)
-
-    if os.environ['RYVEN_MODE'] == 'gui':
-
-        # import the widgets module
-        load_from_file(abs_path)
-
-        # in GUI mode, import the widgets container from NWENV containing all the exported widget classes
-        from ryven import NWENV
-        widgets_container = NWENV.WidgetsRegistry.exported_widgets[-1]
-
-    else:
-        # in non-gui mode, return an object that just returns None for all accessed attributes
-        # so widgets.MyWidget in the nodes file just returns None then
-        class PlaceholderWidgetsContainer:
-            def __getattr__(self, item):
-                return None
-        widgets_container = PlaceholderWidgetsContainer()
-
-    return widgets_container
-
-# ------------------------------------------------------
-
-
-class NodesRegistry:
-    """
-    Stores the nodes exported via export_nodes on import of a nodes package.
-    After running the imported nodes.py module (which causes export_nodes() to run),
-    Ryven can find the exported nodes in exported_nodes.
-    """
-    exported_nodes: [[Node]] = []
-    exported_node_sources: [[str]] = []
-
-
-def export_nodes(*args):
-    """
-    Exports/exposes the specified nodes to Ryven for use in flows.
-    """
-
-    if not isinstance(args, tuple):
-        if issubclass(args, Node):
-            nodes = tuple(args)
-        else:
-            return
-    else:
-        nodes = list(args)
-
-    NodesRegistry.exported_nodes.append(nodes)
-
-    # get sources
-    node_sources = [inspect.getsource(n) for n in nodes]
-    NodesRegistry.exported_node_sources.append(node_sources)
-
-
-# ------------------------------------------------------
diff --git a/ryven/NWENV.py b/ryven/NWENV.py
deleted file mode 100644
index 7bc0f56c..00000000
--- a/ryven/NWENV.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""This module automatically imports all requirements for custom widgets.
-It should lie in the same location as Ryven.py so it can get imported directly from the custom sources
-without path modifications which caused issues in the past."""
-
-import inspect
-
-# types
-Node = None
-IWB = None
-MWB = None
-
-
-def init_node_widget_env():
-    global Node
-    global IWB
-    global MWB
-
-    # for use in the widgets module importing NWENV
-    from ryvencore_qt import \
-        Node as Node_, \
-        IWB as IWB_, \
-        MWB as MWB_
-
-    Node = Node_
-    IWB = IWB_
-    MWB = MWB_
-
-
-class WidgetsRegistry:
-    """
-    Just for holding the widgets specified in export_widgets to access them in NENV.import_widgets
-    without causing naming conflicts with possible a 'exported_widgets' variable in the widgets file.
-    """
-    exported_widgets = []
-    exported_widget_sources: [[str]] = []
-
-
-class WidgetsContainer:
-    pass
-
-
-def export_widgets(*args):
-    """
-    Exports/exposes the specified widgets to the nodes file importing them via import_widgets().
-    Returns an object with all exported widgets as attributes for direct access.
-    """
-
-    widgets = list(args)
-
-    wc = WidgetsContainer()
-    for w in widgets:
-        setattr(wc, w.__name__, w)
-    WidgetsRegistry.exported_widgets.append(wc)
-
-    # get sources
-    widget_sources = [inspect.getsource(w) for w in widgets]
-    WidgetsRegistry.exported_widget_sources.append(widget_sources)
diff --git a/ryven/__init__.py b/ryven/__init__.py
index 55508d78..a802f704 100644
--- a/ryven/__init__.py
+++ b/ryven/__init__.py
@@ -1,11 +1,10 @@
-from ryven.NENV import *
-from ryven.NWENV import *
+from ryven.node_env import *
+from ryven.gui_env import *
 
 import ryven.main.utils as utils
 
 # expose loading nodes package functionality for manual deployment
-from .main.utils import import_nodes_package
-from .main.nodes_package import NodesPackage
+from ryven.main.packages.nodes_package import NodesPackage, import_nodes_package
 
 from .main.Ryven import run as run_ryven
 from .main.RyvenConsole import run as run_ryven_console
diff --git a/ryven/example_nodes/OpenCV/README.md b/ryven/example_nodes/OpenCV/README.md
new file mode 100644
index 00000000..cde5c0f5
--- /dev/null
+++ b/ryven/example_nodes/OpenCV/README.md
@@ -0,0 +1,1 @@
+**TODO: OUTDATED**
\ No newline at end of file
diff --git a/ryven/example_nodes/OpenCV/nodes.py b/ryven/example_nodes/OpenCV/nodes.py
index 378328ee..a41fbfdd 100644
--- a/ryven/example_nodes/OpenCV/nodes.py
+++ b/ryven/example_nodes/OpenCV/nodes.py
@@ -1,6 +1,6 @@
-from ryven.NENV import *
+from ryven.node_env import *
 
-widgets = import_widgets(__file__)
+widgets = import_guis(__file__)
 
 import cv2
 import numpy as np
diff --git a/ryven/example_nodes/OpenCV/widgets.py b/ryven/example_nodes/OpenCV/widgets.py
index 84044cfb..43a2e663 100644
--- a/ryven/example_nodes/OpenCV/widgets.py
+++ b/ryven/example_nodes/OpenCV/widgets.py
@@ -1,4 +1,4 @@
-from ryven.NWENV import *
+from ryven.gui_env import *
 from qtpy.QtWidgets import QLabel, QPushButton, QFileDialog, QVBoxLayout, QWidget, QTextEdit
 from qtpy.QtGui import QImage, QPixmap, QFont
 from qtpy.QtCore import Signal, QSize, QTimer
diff --git a/ryven/example_nodes/auto_generated/autogen.py b/ryven/example_nodes/auto_generated/autogen.py
deleted file mode 100644
index 6c9ea9d8..00000000
--- a/ryven/example_nodes/auto_generated/autogen.py
+++ /dev/null
@@ -1,179 +0,0 @@
-import inspect
-import sys
-import json
-import os
-import importlib
-import distutils.sysconfig as sysconfig
-
-
-def find_builtin_modules():
-    # TODO: improve this
-
-    mods = []
-
-    std_lib = sysconfig.get_python_lib(standard_lib=True)
-    for top, dirs, files in os.walk(std_lib):
-        for nm in files:
-            # if nm == '__init__.py':
-            #     mods.append(os.path.basename(os.path.normpath(top)))
-            if nm != '__init__.py' and nm[-3:] == '.py':
-                mods.append(os.path.join(top, nm)[len(std_lib) + 1:-3].replace(os.sep, '.'))
-
-    return mods
-
-def get_nodes(package_file: str) -> list:
-    nodes = []
-
-    with open(package_file, 'r') as f:
-        package = json.loads(f.read())
-
-        sys.path.append(os.path.dirname(package_file))
-        if 'nodes' in sys.modules.keys():
-            del sys.modules['nodes']
-        mod = __import__('nodes')
-        sys.path.remove(os.path.dirname(package_file))
-
-        for node_name in package['nodes']:
-            nodes.append(mod.__dict__[node_name])
-    
-    return nodes
-
-
-def parse_module(mod_name: str, color: str, target_path: str):
-
-    try:
-        module = __import__(mod_name)
-    except ImportError:
-        return
-
-    routines = []
-
-    for name, obj in inspect.getmembers(module):
-        if inspect.isroutine(obj):
-            routines.append((name, obj))
-    # print(routines)
-    node_defs = []
-
-    node_names = []
-
-    for name, obj in routines:
-        try:
-            sig = inspect.getfullargspec(obj)
-        except Exception as e:
-            # print(e)
-            continue
-
-        # print(f'routine {name} of {mod_name}: {sig}')
-
-        input_signatures = []
-        for i in range(len(sig.args)):
-            arg = sig.args[i]
-            if arg != 'self' and not arg.startswith('_'):
-
-                # default values
-                defaults = sig.defaults
-
-                # only the last n parameters have default values
-                if defaults is None or i < (len(sig.args)-len(defaults)):
-                    input_signatures.append(
-                        f'NodeInputBP(label=\'{arg}\'),'
-                    )
-                else:
-                    j = i - (len(sig.args)-len(defaults))
-                    v = sig.defaults[j]
-
-                    # make sure the type is correctly converted to a valid code string
-                    if type(v) == type:
-                        default_val = v.__name__
-
-                    elif type(v) == str:
-                        default_val = f'\'{v}\''
-
-                    elif type(v) == object or inspect.isfunction(v):  # seems to be a bug
-                        default_val = None
-
-                    else:
-                        default_val = v
-
-                    input_signatures.append(
-                        f'NodeInputBP(label=\'{arg}\', dtype=dtypes.Data(default={default_val}, size=\'s\')),'
-                    )
-
-        inputs = '\n        '.join(input_signatures)
-
-        node_name = f'{name.title()}_Node'
-        node_names.append(node_name)
-        doc = obj.__doc__.replace('\0', '<NULL>') if obj.__doc__ else ''
-
-        input_calls = ', '.join([f'self.input({i})' for i in range(len(input_signatures))])
-        update_content = f'self.set_output_val(0, {mod_name}.{name}({ input_calls }))'
-
-        node_def = f'''
-class {node_name}(NodeBase):
-    """
-    {doc}"""
-    
-    title = \'{name}\'
-    type_ = \'{mod_name}\'
-    init_inputs = [
-        {inputs}
-    ]
-    init_outputs = [
-        NodeOutputBP(type_='data'),
-    ]
-    color = \'{color}\'
-
-    def update_event(self, inp=-1):
-        {update_content}
-        '''
-
-        node_defs.append(node_def)
-
-    node_names_tuple_str = '\n    '.join([f'{name},' for name in node_names])
-
-    if not os.path.exists(target_path):
-        os.mkdir(target_path)
-
-    with open(target_path+'/nodes.py', 'w') as f:
-        f.write('''
-from ryven.NENV import *
-
-import '''+mod_name+'''
-
-
-class NodeBase(Node):
-    pass
-
-'''+'\n'.join(node_defs)+f'''
-
-
-export_nodes(
-    {node_names_tuple_str}
-)
-''')
-
-    package = {
-        'type': 'Ryven auto generated nodes package',
-        'nodes': node_names
-    }
-
-    try:
-        print(target_path+'/'+mod_name+'.rpc')
-        os.remove(target_path+'/'+mod_name+'.rpc')
-    except OSError:
-        pass
-
-
-if __name__ == '__main__':
-
-    if len(sys.argv) > 1:
-        mod_name = sys.argv[1]
-        color = sys.argv[2]
-        target_path = sys.argv[3] if len(sys.argv) > 3 else mod_name
-        parse_module(mod_name, color, target_path)
-    else:
-        builtin_modules = find_builtin_modules()
-        # sys.builtin_module_names is missing many like "random"
-        for m in builtin_modules:
-            parse_module(m, '#32DA22', m)
-
diff --git a/ryven/example_nodes/linalg/README.md b/ryven/example_nodes/linalg/README.md
new file mode 100644
index 00000000..01885ff3
--- /dev/null
+++ b/ryven/example_nodes/linalg/README.md
@@ -0,0 +1,4 @@
+This nodes package provides nodes for linear algebra operations on matrices using numpy.
+It implements only a small subset of the numpy functions, but it should be easy to add more.
+This is a good example of how to implement a nodes package for a specific library.
+Ryven comes with a little example project showcasing this package.
\ No newline at end of file
diff --git a/ryven/example_nodes/linalg/widgets.py b/ryven/example_nodes/linalg/gui.py
similarity index 63%
rename from ryven/example_nodes/linalg/widgets.py
rename to ryven/example_nodes/linalg/gui.py
index 247e512d..24e87239 100644
--- a/ryven/example_nodes/linalg/widgets.py
+++ b/ryven/example_nodes/linalg/gui.py
@@ -1,27 +1,28 @@
-from ryven.NWENV import *
+from ryven.gui_env import *
+
 from qtpy.QtWidgets import QTextEdit
 from qtpy.QtGui import QFontMetrics
 
 import numpy as np
 
 
-class MatrixWidget(MWB, QTextEdit):
+class MatrixWidget(NodeMainWidget, QTextEdit):
 
     def __init__(self, params, base_width=50, base_height=50):
-        MWB.__init__(self, params)
+        NodeMainWidget.__init__(self, params)
         QTextEdit.__init__(self)
 
-        c = self.node.color
-        self.setStyleSheet('''
-QTextEdit{
-    color: '''+c+''';
+        c = self.node.gui.color
+        self.setStyleSheet(f'''
+QTextEdit{{
+    color: {c};
     background: transparent;
     border: none;
     border-radius: 4px;
     padding: 0px;
     font-family: Source Code Pro;
     font-size: 10pt;
-}
+}}
         ''')  
         # border: 1px solid '''+c+''';
         
@@ -32,6 +33,10 @@ def __init__(self, params, base_width=50, base_height=50):
         self.setReadOnly(True)
         self.hidden_size = None
 
+    @property
+    def gui(self):
+        return self.node.gui
+
     def update_matrix(self, m):
         if m is None:
             self.setText('')
@@ -94,23 +99,24 @@ def resize_to_content(self, lines):
         text_height = fm.height()*(len(lines))+15  # vertical buffer for padding etc.
         self.setFixedWidth(text_width if text_width > self.base_width else self.base_width)
         self.setFixedHeight(text_height if text_height > self.base_height else self.base_height)
-        self.node.update_shape()
+        self.gui.update_shape()
 
     def hide(self):
         self.hidden_size = self.size()
         self.setFixedSize(0, 0)
-        self.node.update_shape()
+        self.gui.update_shape()
 
     def show(self):
         self.setFixedSize(self.hidden_size)
-        self.node.update_shape()
+        self.gui.update_shape()
         self.hidden_size = None
 
 
     def get_state(self):
-        data = {'text': self.toPlainText(),
-                'shown': self.hidden_size is None
-                }
+        data = {
+            'text': self.toPlainText(),
+            'shown': self.hidden_size is None
+        }
         return data
 
     def set_state(self, data):
@@ -120,7 +126,7 @@ def set_state(self, data):
             self.hide()
 
 
-class MatrixNode_MainWidget(MatrixWidget):
+class EditMatrixWidget(MatrixWidget):
     def __init__(self, params):
         super().__init__(params, 100, 80)
 
@@ -136,7 +142,57 @@ def focusOutEvent(self, e):
         QTextEdit.focusOutEvent(self, e)
 
 
-export_widgets(
-    MatrixWidget,
-    MatrixNode_MainWidget,
-)
+class MatrixNodeBaseGui(NodeGUI):
+    main_widget_class = MatrixWidget
+    main_widget_pos = 'below ports'
+    color = '#3344ff'
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.main_widget_hidden = False
+
+    def initialized(self):
+        if 'show preview' not in self.actions:  # this happens when loading a project
+            self.actions['hide preview'] = {'method': self.ac_hide_mw}
+
+    def show_matrix(self, mat):
+        self.main_widget().update_matrix(mat)
+
+    def ac_hide_mw(self):
+        self.main_widget().hide()
+        del self.actions['hide preview']
+        self.actions['show preview'] = {'method': self.ac_show_mw}
+        self.main_widget_hidden = True
+        self.update_shape()
+
+    def ac_show_mw(self):
+        self.main_widget().show()
+        del self.actions['show preview']
+        self.actions['hide preview'] = {'method': self.ac_hide_mw}
+        self.main_widget_hidden = False
+        self.update_shape()
+
+    def get_state(self):
+        return {
+            **super().get_state(),
+            'main_widget_hidden': self.main_widget_hidden
+        }
+
+    def set_state(self, data):
+        super().set_state(data)
+        if data['main_widget_hidden']:
+            self.ac_hide_mw()
+        # shown by default
+
+
+class EditMatrixNodeGui(MatrixNodeBaseGui):
+    main_widget_class = EditMatrixWidget
+    main_widget_pos = 'below ports'
+    color = '#3344ff'
+
+
+export_guis([
+    MatrixNodeBaseGui,
+    EditMatrixNodeGui,
+])
diff --git a/ryven/example_nodes/linalg/matrix_widget.py b/ryven/example_nodes/linalg/matrix_widget.py
deleted file mode 100644
index db242b25..00000000
--- a/ryven/example_nodes/linalg/matrix_widget.py
+++ /dev/null
@@ -1,159 +0,0 @@
-from ryven.NWENV import *
-
-from qtpy.QtWidgets import QTextEdit
-# from qtpy.QtCore import ...
-from qtpy.QtGui import QFontMetrics, QFont
-
-import numpy as np
-
-
-class MatrixWidget(QTextEdit, MWB):
-    def __init__(self, params, base_width=50, base_height=50):
-        MWB.__init__(self, params)
-        QTextEdit.__init__(self)
-
-
-
-        c = self.parent_node_instance.color.name()
-        self.setStyleSheet('''
-QTextEdit{
-    color: '''+c+''';
-    background: transparent;
-    border: 1px solid '''+c+''';
-    border-radius: 4px;
-}
-        ''')
-        self.setFont(QFont('source code pro', 10))
-        self.base_width = base_width
-        self.base_height = base_height
-        self.setFixedSize(self.base_width, self.base_height)
-        self.setReadOnly(True)
-        self.hidden_size = None
-
-    #     self.textChanged.connect(M(self.text_changed))
-    #
-    #
-    # def text_changed(self, new_text):
-    #     fm = QFontMetrics(self.font())
-    #     text_width = fm.width(new_text)
-    #     new_width = text_width+15
-    #     self.setFixedWidth(new_width if new_width > self.base_width else self.base_width)
-    #
-    #     self.parent_node_instance.update_shape()
-    #     self.parent_node_instance.rebuild_ui()  # see rebuild_ui() for explanation
-
-    def update_matrix(self, m):
-        if m is None:
-            self.setText('')
-            return
-
-        longest_exp_length = -1
-        lines = []
-
-        try:
-            matrix = np.around(m, 4)
-        except Exception:
-            matrix = m
-            # non computable matrix (expression matrix)
-
-            # if matrix.ndim == 1:
-            #     longest_exp_length = self.get_row_lxl(matrix)
-            #     lines.append(self.format_row_to_str(matrix, longest_exp_length))
-            # elif matrix.ndim == 2:
-            #     for row in matrix:
-            #         nlxl = self.get_row_lxl(row)
-            #         if nlxl > longest_exp_length:
-            #             longest_exp_length = nlxl
-            #     for row in matrix:
-            #         lines.append(self.format_row_to_str(row, longest_exp_length))
-            #
-            # self.setText('\n'.join(lines))
-            # return
-            pass
-
-        if matrix.ndim == 0:    # can happen with scalars
-            lines = [str(m)]
-
-        elif matrix.ndim == 1:
-            longest_exp_length = self.get_row_lxl(matrix)
-            row = matrix
-            lines.append(self.format_row_to_str(row, longest_exp_length))
-
-        elif matrix.ndim == 2:
-            for row in matrix:
-                nlxl = self.get_row_lxl(row)
-                if nlxl > longest_exp_length:
-                    longest_exp_length = nlxl
-
-            for row in matrix:
-                lines.append(self.format_row_to_str(row, longest_exp_length))
-
-
-        s = '\n'.join(lines)
-        self.setText(s)
-        self.resize_to_content(lines)
-
-    def get_row_lxl(self, row):
-        longest_exp_length = -1
-        for exp in row:
-            if type(exp) == str:
-                if len(exp) > longest_exp_length:
-                    longest_exp_length = len(exp)
-            else:
-                if len(str(exp)) > longest_exp_length:  # round(number, 4)
-                    longest_exp_length = len(str(exp))
-        return longest_exp_length
-
-    def format_row_to_str(self, row, lxl):
-        format_str = len(row) * ('{:>'+str(lxl)+'} ')
-        format_str = format_str[:-1]
-        return format_str.format(*row)
-
-    def resize_to_content(self, lines):
-        if len(lines) == 0:
-            lines.append('')
-
-        # resize properly
-        fm = QFontMetrics(self.font())
-        text_width = fm.width(lines[0]+'__')
-        text_width = text_width+20  # some buffer
-        text_height = fm.height()*(len(lines))+12  # also some vertical buffer
-        self.setFixedWidth(text_width if text_width > self.base_width else self.base_width)
-        self.setFixedHeight(text_height if text_height > self.base_height else self.base_height)
-        self.parent_node_instance.update_shape()
-
-        # if self.hidden_size is None:
-        #     # resize properly
-        #     fm = QFontMetrics(self.font())
-        #     text_width = fm.width(lines[0]+'__')
-        #     text_width = text_width+20  # some buffer
-        #     text_height = fm.height()*(len(lines))+12  # also some vertical buffer
-        #     self.setFixedWidth(text_width if text_width > self.base_width else self.base_width)
-        #     self.setFixedHeight(text_height if text_height > self.base_height else self.base_height)
-        #     self.parent_node_instance.update_shape()
-
-    def hide(self):
-        self.hidden_size = self.size()
-        self.setFixedSize(0, 0)
-        self.parent_node_instance.update_shape()
-
-    def show(self):
-        self.setFixedSize(self.hidden_size)
-        self.parent_node_instance.update_shape()
-        self.hidden_size = None
-
-
-    def get_state(self):
-        data = {'text': self.toPlainText(),
-                'shown': self.hidden_size is None
-                }
-        return data
-
-    def set_state(self, data):
-        self.setText(data['text'])
-        self.resize_to_content(data['text'].splitlines())
-        if not data['shown']:
-            self.hide()
-
-    def remove_event(self):
-        pass
diff --git a/ryven/example_nodes/linalg/nodes.py b/ryven/example_nodes/linalg/nodes.py
index e5459b94..d603fde8 100644
--- a/ryven/example_nodes/linalg/nodes.py
+++ b/ryven/example_nodes/linalg/nodes.py
@@ -1,163 +1,77 @@
-from ryven.NENV import *
-widgets = import_widgets(__file__)
+from typing import Optional, List
+
+import ryvencore.addons.Variables
+
+from ryven.node_env import *
+guis = import_guis(__file__)
 
 import numpy as np
+from itertools import chain
+
+
+class MatrixData(Data):
+    # currently, there should not be any implicit
+    # data sharing between nodes, because their
+    # operations are copying the data anyway
+
+    # notice that numpy arrays are pickle serializable
+
+    pass
 
 
 class MatrixNodeBase(Node):
-    main_widget_class = widgets.MatrixWidget
-    main_widget_pos = 'below ports'
+    """
+    Base class for nodes handling matrices.
+    It implements basic forward propagation of a mutated matrix,
+    defined by some operation in the get_mat() method, and a GUI
+    for displaying the matrix.
+    """
+
+    version = 'v0.2'
     init_inputs = [
-        NodeInputBP()
+        NodeInputType()
     ]
     init_outputs = [
-        NodeOutputBP()
+        NodeOutputType()
     ]
-    color = '#3344ff'
+    GUI = guis.MatrixNodeBaseGui
 
     def __init__(self, params):
         super().__init__(params)
         self.mat = None
 
-    def show_matrix(self):
-        if self.session.gui and self.main_widget():
-            self.main_widget().update_matrix(self.mat)
-    
-    def update_event(self, inp=-1):
-        self.mat = self.get_mat()
-        self.show_matrix()
-        self.set_output_val(0, self.mat)
-
-
-class ShowMatrix(MatrixNodeBase):
-    """Displays a matrix"""
-
-    title = 'Show Matrix'
-
     def get_mat(self):
-        return self.input(0)
-
-# ------------------------------------------------------------------------------
-
-
-class Matrix_Node(Node):
-    """Evaluates a matrix"""
-    title = 'Matrix'
-    init_inputs = []
-    init_outputs = [
-        NodeOutputBP()
-    ]
-    main_widget_class = widgets.MatrixNode_MainWidget
-    main_widget_pos = 'below ports'
-    color = '#3344ff'
+        """
+        Returns the processed matrix.
+        Pre: self.inputs_ready() is True.
+        """
+        raise NotImplementedError
 
-    def __init__(self, params):
-        super(Matrix_Node, self).__init__(params)
-
-        self.actions['hide preview'] = {'method': self.action_hide_mw}
-        self.main_widget_hidden = False
-        self.expression_matrix = None
-        self.evaluated_matrix = None
-        self.used_variable_names = []
-
-    def place_event(self):
-        self.update()
+    def inputs_ready(self):
+        """Returns True if no input is None."""
+        return all(self.input(i) is not None for i in range(len(self.inputs)))
 
     def update_event(self, inp=-1):
-        self.set_output_val(0, self.evaluated_matrix)
-        
-    def parse_matrix(self, s):
-        lines = s.splitlines()
-        # the list(filter(...)) creates an array of strings for every line
-        try:
-            self.expression_matrix = np.array([[exp for exp in list(filter(lambda s: s != '', l.split(' ')))] for l in lines])
-            self.eval_expression_matrix()
-            self.update()
-        except ValueError:
-            # something like 2+ (which could become 2+1j) can't get parsed yet
+        if not self.inputs_ready():
             return
 
-    def eval_expression_matrix(self):
-        if not self.register_vars(self.expression_matrix):
-            return  # return if parsing failed
-
-        self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
-
-        if self.evaluated_matrix is None:
-            return  # matrix could not be evaluated
-        # custom_array = [list(map(number_type, list(filter(lambda s: s != '', l.split(' '))))) for l in lines]
-
-        try:
-            self.evaluated_matrix = np.array(self.evaluated_matrix)
-        except Exception:
-            return
-
-    def eval_matrix(self, lines):
-        v = self.get_var_val
-        evaled_exp_array = []
-        for l in lines:
-            evaled_exp_array.append([])
-            for exp in l:
-                evaled_exp_array[-1].append(eval(exp))
-        float_exp_array = [[float(exp) if type(exp) == int else exp for exp in l] for l in evaled_exp_array]
-        return np.array(float_exp_array)
-
-    def register_vars(self, lines):
-        try:
-            # clear used variables
-            for name in self.used_variable_names:
-                self.unregister_var_receiver(name, self.var_val_updated)
-            self.used_variable_names.clear()
-
-            v = self.register_variable
-            for l in lines:
-                for exp in l:
-                    eval(exp)
-            return True
-        except Exception:
-            return False
-
-    def register_variable(self, name):
-        # connect to variable changes
-        self.register_var_receiver(name, self.var_val_updated)
-        self.used_variable_names.append(name)
+        self.mat = self.get_mat()
 
-    def var_val_updated(self, name, val):
-        self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
-        self.update()
+        if self.have_gui():
+            self.gui.show_matrix(self.mat)
 
-    def action_hide_mw(self):
-        self.main_widget().hide()
-        del self.actions['hide preview']
-        self.actions['show preview'] = {'method': self.action_show_mw}
-        self.main_widget_hidden = True
-        self.update_shape()
+        self.set_output_val(0, MatrixData(self.mat))
 
-    def action_show_mw(self):
-        self.main_widget().show()
-        del self.actions['show preview']
-        self.actions['hide preview'] = {'method': self.action_hide_mw}
-        self.main_widget_hidden = False
-        self.update_shape()
+    def have_gui(self):
+        return hasattr(self, 'gui')
 
-    def get_state(self):
-        expression_matrix_list = self.expression_matrix
-        if expression_matrix_list is not None:  # ndarrays are not json serializaple
-            expression_matrix_list = expression_matrix_list.tolist()
 
-        data = {'main widget hidden': self.main_widget_hidden,
-                'expression matrix': expression_matrix_list}
-        return data
+class ShowMatrix(MatrixNodeBase):
+    """Simply displays a matrix"""
+    title = 'Show Matrix'
 
-    def set_state(self, data, version):
-        if self.session.gui:
-            self.main_widget_hidden = data['main widget hidden']
-            if self.main_widget_hidden:
-                self.action_hide_mw()
-            # shown by default
-        
-        self.expression_matrix = np.array(data['expression matrix'])
-        self.eval_expression_matrix()
+    def get_mat(self):
+        return self.input(0).payload
 
 
 class Conjugate(MatrixNodeBase):
@@ -165,7 +79,7 @@ class Conjugate(MatrixNodeBase):
     title = 'Conjugate'
 
     def get_mat(self):
-        return np.conjugate(self.input(0))
+        return np.conjugate(self.input(0).payload)
 
 
 class Transpose(MatrixNodeBase):
@@ -173,7 +87,7 @@ class Transpose(MatrixNodeBase):
     title = 'Transpose'
 
     def get_mat(self):
-        return np.transpose(self.input(0))
+        return np.transpose(self.input(0).payload)
 
 
 class DetOfMatrix(MatrixNodeBase):
@@ -181,19 +95,22 @@ class DetOfMatrix(MatrixNodeBase):
     title = 'Determinant'
 
     def get_mat(self):
-        return np.linalg.det(self.input(0))
+        return np.linalg.det(self.input(0).payload)
 
 
 class DotProduct(MatrixNodeBase):
     """Computes the dot product of a matrix."""
     title = 'Dot Product'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
     
     def get_mat(self):
-        return np.dot(self.input(0), self.input(1))
+        return np.dot(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class HermMatrix(MatrixNodeBase):
@@ -201,7 +118,7 @@ class HermMatrix(MatrixNodeBase):
     title = 'Herm'
 
     def get_mat(self):
-        return np.transpose(np.conjugate(self.input(0)))
+        return np.transpose(np.conjugate(self.input(0).payload))
 
 
 class IDMatrix(MatrixNodeBase):
@@ -209,7 +126,7 @@ class IDMatrix(MatrixNodeBase):
     title = 'ID Matrix'
 
     def get_mat(self):
-        return np.identity(self.input(0))
+        return np.identity(self.input(0).payload)
 
 
 class ImagMatrix(MatrixNodeBase):
@@ -217,7 +134,7 @@ class ImagMatrix(MatrixNodeBase):
     title = 'Imag'
 
     def get_mat(self):
-        return np.imag(self.input(0))
+        return np.imag(self.input(0).payload)
 
 
 class RealMatrix(MatrixNodeBase):
@@ -225,31 +142,37 @@ class RealMatrix(MatrixNodeBase):
     title = 'Real'
 
     def get_mat(self):
-        return np.real(self.input(0))
+        return np.real(self.input(0).payload)
 
 
 class InnerProduct(MatrixNodeBase):
     """Computes the inner product of the input matrices."""
     title = 'Inner'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
     
     def get_mat(self):
-        return np.inner(self.input(0), self.input(1))
+        return np.inner(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class OuterProduct(MatrixNodeBase):
     """Creates the outer product of two matrices."""
     title = 'Outer'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
 
     def get_mat(self):
-        return np.outer(self.input(0), self.input(1))
+        return np.outer(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class InverseMatrix(MatrixNodeBase):
@@ -257,7 +180,9 @@ class InverseMatrix(MatrixNodeBase):
     title = 'Inverse'
 
     def get_mat(self):
-        return np.linalg.inv(self.input(0))
+        return np.linalg.inv(
+            self.input(0).payload
+        )
 
 
 class KronMatrix(MatrixNodeBase):
@@ -265,7 +190,10 @@ class KronMatrix(MatrixNodeBase):
     title = 'Kron'
 
     def get_mat(self):
-        return np.kron(self.input(0), self.input(1))
+        return np.kron(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class MaskLower(MatrixNodeBase):
@@ -273,7 +201,9 @@ class MaskLower(MatrixNodeBase):
     title = 'Mask Lower'
 
     def get_mat(self):
-        return np.tril(self.input(0))
+        return np.tril(
+            self.input(0).payload
+        )
 
 
 class MaskUpper(MatrixNodeBase):
@@ -281,31 +211,39 @@ class MaskUpper(MatrixNodeBase):
     title = 'Mask Upper'
 
     def get_mat(self):
-        return np.triu(self.input(0))
+        return np.triu(
+            self.input(0).payload
+        )
 
 
 class MatMul(MatrixNodeBase):
     """Performs a matrix multiplication."""
     title = 'Mult'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
 
     def get_mat(self):
-        return np.matmul(self.input(0), self.input(1))
+        return np.matmul(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class MatPower(MatrixNodeBase):
     """Powers a matrix."""
     title = 'Power'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
 
     def get_mat(self):
-        return np.linalg.matrix_power(self.input(0), self.input(1))
+        return np.linalg.matrix_power(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class NullMatrix(MatrixNodeBase):
@@ -313,7 +251,7 @@ class NullMatrix(MatrixNodeBase):
     title = 'Null'
 
     def get_mat(self):
-        return np.zeros(shape=(self.input(0), self.input(1)))
+        return np.zeros(shape=(self.input(0).payload, self.input(1).payload))
 
 
 class OnesMatrix(MatrixNodeBase):
@@ -321,36 +259,188 @@ class OnesMatrix(MatrixNodeBase):
     title = 'Ones'
 
     def get_mat(self):
-        return np.ones(shape=(self.input(0), self.input(1)))
+        return np.ones(shape=(self.input(0).payload, self.input(1).payload))
 
 
 class RandomMatrix(MatrixNodeBase):
     """Creates a matrix with random values between 0 and 1."""
     title = 'Rand'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
 
     def get_mat(self):
-        m = np.random.rand(self.input(0), self.input(1))
-        return m
+        return np.random.rand(
+            self.input(0).payload,
+            self.input(1).payload
+        )
 
 
 class SolveLEq(MatrixNodeBase):
     """Solves a linear equation system."""
     title = 'Solve'
     init_inputs = [
-        NodeInputBP(),
-        NodeInputBP(),
+        NodeInputType(),
+        NodeInputType(),
     ]
 
     def get_mat(self):
-        return np.linalg.solve(self.input(0), self.input(1))
+        return np.linalg.solve(
+            self.input(0).payload,
+            self.input(1).payload
+        )
+
+
+class EditMatrixNode(Node):
+    """
+    A special node for hand-designing matrices with specific
+    numerical values. It also supports Ryven variables and
+    automatically adapts the matrix when they change.
+    """
+    title = 'Matrix'
+    identifier = 'EditMatrix_Node'
+    legacy_identifiers = ['Matrix_Node', 'linalg.Matrix_Node']
+    init_inputs = []
+    init_outputs = [
+        NodeOutputType()
+    ]
+    GUI = guis.EditMatrixNodeGui
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.expression_matrix: Optional[np.ndarray] = None
+        self.evaluated_matrix: Optional[MatrixData] = None
+
+        self.used_variable_names = []
+
+    def update_event(self, inp=-1):
+        if self.expression_matrix is not None:
+            self.set_output_val(0, self.evaluated_matrix)
+
+    def parse_matrix(self, s):  # called from gui
+        lines = s.splitlines()
+
+        try:
+            # this throws a ValueError if the matrix is not rectangular
+            exp_mat = np.array([
+                list(filter(lambda s: s != '', l.split(' ')))  # array of expression strings
+                for l in lines
+            ])
+
+            # check if all expressions are valid
+            for exp in self.flatten_2d(exp_mat):
+                if not self.exp_is_var(exp):
+                    eval(exp)
+                elif not self.var_exists(exp):
+                    raise NameError
+        except (ValueError, NameError):
+            # something like 2+ (which could become 2+1j) can't get parsed yet
+            return
+
+        self.expression_matrix = exp_mat
+        self.register_vars_and_eval_matrix()
+        self.update()
+
+    def register_vars_and_eval_matrix(self):
+        if not self.register_vars(self.expression_matrix):
+            return  # abort if vars registration failed
+        self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
+
+    def register_vars(self, lines: np.ndarray) -> bool:
+        """Updates subscriptions for the variables used in the matrix."""
+
+        for _ in range(len(self.used_variable_names)):
+            self.unsub_var(self.used_variable_names[0])
+
+        var_names = set()
+        for exp in filter(self.exp_is_var, self.flatten_2d(lines)):
+            if self.var_exists(exp):
+                var_names.add(exp)
+            else:
+                return False
+
+        for var_name in var_names:
+            self.sub_var(var_name)
+
+        return True
+
+    def eval_matrix(self, lines) -> MatrixData:
+        """
+        Evaluates a matrix from string expressions of numerals and variables.
+        """
+
+        # replace old v('name') syntax with new 'name' syntax
+        lines = [
+            [exp.replace('v(', '').replace(')', '') if 'v(' in exp else exp
+             for exp in l]
+            for l in lines
+        ]
+
+        # evaluate expressions
+        evaled_exp_array = [
+            [
+                eval(exp)
+                if not self.exp_is_var(exp) else
+                self.var_val(exp)
+                for exp in l
+            ]
+            for l in lines
+        ]
+
+        # convert ints to floats; leave complex numbers
+        int_to_float = lambda v: float(v) if isinstance(v, int) else v
+        float_exp_array = [
+            list(map(int_to_float, l))
+            for l in evaled_exp_array
+        ]
+
+        # build matrix and wrap in MatrixData
+        return MatrixData(np.array(float_exp_array))
+
+    def var_exists(self, name):
+        return self.get_addon('Variables').var_exists(self.flow, name)
+
+    def sub_var(self, name) -> bool:
+        if not self.var_exists(name):
+            return False
+
+        self.get_addon('Variables').subscribe(self, name, self.var_val_updated)
+        self.used_variable_names.append(name)
+        return True
+
+    def unsub_var(self, name):
+        self.get_addon('Variables').unsubscribe(self, name, self.var_val_updated)
+        self.used_variable_names.remove(name)
+
+    def var_val_updated(self, var: ryvencore.addons.Variables.Variable):
+        self.evaluated_matrix = self.eval_matrix(self.expression_matrix)
+        self.update()
+
+    def var(self, name) -> ryvencore.addons.Variables.Variable:
+        return self.get_addon('Variables').var(self.flow, name)
+
+    def var_val(self, name):
+        return self.var(name).get()
+
+    def exp_is_var(self, s: str):
+        return s.isidentifier()
+
+    def flatten_2d(self, mat: np.ndarray) -> np.ndarray:
+        return np.array(list(chain(*mat)))
+
+    def get_state(self):
+        data = {'expression matrix': serialize(self.expression_matrix)}
+        return data
+
+    def set_state(self, data, version):
+        self.expression_matrix = deserialize(data['expression matrix'])
+        self.register_vars_and_eval_matrix()
 
 
-export_nodes(
-    Matrix_Node,
+export_nodes([
+    EditMatrixNode,
     ShowMatrix,
     Conjugate,
     Transpose,
@@ -372,4 +462,6 @@ def get_mat(self):
     OnesMatrix,
     RandomMatrix,
     SolveLEq,
+],
+    data_types=[MatrixData]
 )
diff --git a/ryven/example_nodes/std/README.md b/ryven/example_nodes/std/README.md
new file mode 100644
index 00000000..d3bbcc3a
--- /dev/null
+++ b/ryven/example_nodes/std/README.md
@@ -0,0 +1,3 @@
+This package contains a bunch of nodes for different purposes.
+It showcases many features of Ryven's nodes system and tries to push the boundaries.
+Some easy nodes are found in `basic_operators.py`, while more advanced ideas are implemented in `special_nodes.py`.
\ No newline at end of file
diff --git a/ryven/example_nodes/std/basic_operators.py b/ryven/example_nodes/std/basic_operators.py
index 191dba22..5b41221b 100644
--- a/ryven/example_nodes/std/basic_operators.py
+++ b/ryven/example_nodes/std/basic_operators.py
@@ -1,76 +1,56 @@
-from ryven.NENV import *
+from ryven.node_env import *
 
-# import math
+guis = import_guis(__file__)
 
 
 class OperatorNodeBase(Node):
+    """
+    Base class for nodes implementing a binary operation.
+    """
 
-    version = 'v0.0'
-
+    version = 'v0.2'
     init_inputs = [
-        NodeInputBP(dtype=dtypes.Data(size='s')),
-        NodeInputBP(dtype=dtypes.Data(size='s')),
+        NodeInputType(),
+        NodeInputType(),
     ]
-
     init_outputs = [
-        NodeOutputBP(),
+        NodeOutputType(),
     ]
-
-    style = 'small'
+    GUI = guis.OperatorNodeBaseGui
 
     def __init__(self, params):
         super().__init__(params)
 
         self.num_inputs = 0
-        self.actions['add input'] = {'method': self.add_operand_input}
 
     def place_event(self):
-        for i in range(len(self.inputs)):
-            self.register_new_operand_input(i)
+        self.num_inputs = len(self.inputs)
 
-    def add_operand_input(self):
-        self.create_input_dt(dtype=dtypes.Data(size='s'))
-        self.register_new_operand_input(self.num_inputs)
-        self.update()
+    def add_op_inp(self):
+        self.create_input()
+        self.num_inputs += 1
 
-    def remove_operand_input(self, index):
+    def remove_op_input(self, index):
         self.delete_input(index)
         self.num_inputs -= 1
-        # del self.actions[f'remove input {index}']
-        self.rebuild_remove_actions()
-        self.update()
-
-    def register_new_operand_input(self, index):
-        self.actions[f'remove input {index}'] = {
-            'method': self.remove_operand_input,
-            'data': index
-        }
-        self.num_inputs += 1
-
-    def rebuild_remove_actions(self):
-
-        remove_keys = []
-        for k, v in self.actions.items():
-            if k.startswith('remove input'):
-                remove_keys.append(k)
-
-        for k in remove_keys:
-            del self.actions[k]
-
-        for i in range(self.num_inputs):
-            self.actions[f'remove input {i}'] = {'method': self.remove_operand_input, 'data': i}
 
     def update_event(self, inp=-1):
-        self.set_output_val(0, self.apply_op([self.input(i) for i in range(len(self.inputs))]))
+        self.set_output_val(0, Data(self.apply_op([
+            self.input(i).payload
+            for i in range(len(self.inputs))
+        ])))
 
     def apply_op(self, elements: list):
         return None
 
 
-# LOGIC -------------------------------------
+"""
+    logical operators
+"""
+
 
 class LogicNodeBase(OperatorNodeBase):
-    color = '#f58142'
+    GUI = guis.LogicNodeBaseGui
 
 
 class NOT_Node(LogicNodeBase):
@@ -136,13 +116,14 @@ def apply_op(self, elements: list):
     XNOR_Node,
 ]
 
-# -------------------------------------------
 
+"""
+    arithmetic operators
+"""
 
-# ARITHMETIC --------------------------------
 
 class ArithmeticNodeBase(OperatorNodeBase):
-    color = '#58db53'
+    GUI = guis.ArithNodeBaseGui
 
 
 class Plus_Node(ArithmeticNodeBase):
@@ -186,13 +167,6 @@ def apply_op(self, elements: list):
         for e in elements[1:]:
             v = v / e
         return v
-        # if len(elements) > 0:
-        #     x = elements[0]
-        #     for e in elements[1:]:
-        #         x /= e
-        #     return x
-        # else:
-        #     return None
 
 
 class Power_Node(ArithmeticNodeBase):
@@ -203,13 +177,6 @@ def apply_op(self, elements: list):
         for e in elements[1:]:
             v = v ** e
         return v
-        # if len(elements) > 0:
-        #     x = elements[0]
-        #     for e in elements[1:]:
-        #         x **= e
-        #     return x
-        # else:
-        #     return None
 
 
 arithmetic_nodes = [
@@ -220,13 +187,14 @@ def apply_op(self, elements: list):
     Power_Node,
 ]
 
-# -------------------------------------------
 
+"""
+    comparison operators
+"""
 
-# COMPARATORS -------------------------------
 
 class ComparatorNodeBase(OperatorNodeBase):
-    color = '#a1574c'
+    GUI = guis.CompNodeBaseGui
 
     def apply_op(self, elements: list):
         # if len(elements) > 0:
@@ -291,7 +259,10 @@ def comp(self, a, b) -> bool:
     LessEq_Node,
 ]
 
-# -------------------------------------------
+
+"""
+    export
+"""
 
 
 nodes = [
diff --git a/ryven/example_nodes/std/control_structures.py b/ryven/example_nodes/std/control_structures.py
index 6a595bcb..9dd19d09 100644
--- a/ryven/example_nodes/std/control_structures.py
+++ b/ryven/example_nodes/std/control_structures.py
@@ -1,27 +1,26 @@
-from ryven.NENV import *
+from ryven.node_env import *
 
+guis = import_guis(__file__)
 
-class CSNodeBase(Node):
-    version = 'v0.1'
-    style = 'normal'
-    color = '#b33a27'
 
+class CSNodeBase(Node):
+    version = 'v0.2'
+    GUI = guis.CSNodeBaseGui
 
 class If_Node(CSNodeBase):
     title = 'branch'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.Boolean(), label='cond'),
+        NodeInputType(type_='exec'),
+        NodeInputType(label='cond'),
     ]
     init_outputs = [
-        NodeOutputBP('true', type_='exec', ),
-        NodeOutputBP('false', type_='exec'),
+        NodeOutputType('true', type_='exec', ),
+        NodeOutputType('false', type_='exec'),
     ]
 
     def update_event(self, inp=-1):
         if inp == 0:
-            if self.input(1):
+            if self.input(1).payload:
                 self.exec_output(0)
             else:
                 self.exec_output(1)
@@ -31,38 +30,31 @@ class ForLoop_Node(CSNodeBase):
     """n dimensional for loop!"""
 
     title = 'for'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.Integer(), label='from'),
-        NodeInputBP(dtype=dtypes.Integer(), label='to'),
+        NodeInputType(type_='exec'),
+        NodeInputType(label='from'),
+        NodeInputType(label='to'),
     ]
     init_outputs = [
-        NodeOutputBP('loop', type_='exec'),
-        NodeOutputBP('i', type_='data'),
-        NodeOutputBP('finished', type_='exec'),
+        NodeOutputType('loop', type_='exec'),
+        NodeOutputType('i', type_='data'),
+        NodeOutputType('finished', type_='exec'),
     ]
+    GUI = guis.ForLoopGui
 
     def __init__(self, params):
         super().__init__(params)
 
-        self.actions['add dimension'] = {'method': self.add_dimension}
-
         self.dims = 1
 
     def add_dimension(self):
         new_dim = self.dims + 1
-        self.create_input_dt(dtype=dtypes.Integer(), label=f'{new_dim} from')
-        self.create_input_dt(dtype=dtypes.Integer(), label=f'{new_dim} to')
+        self.create_input(label=f'{new_dim} from')
+        self.create_input(label=f'{new_dim} to')
         self.create_output(f'loop {new_dim}', 'exec', insert=-1)
         self.create_output(f'i {new_dim}', 'data', insert=-1)
         self.dims += 1
 
-        self.actions[f'remove dimension {new_dim}'] = {
-            'method': self.remove_dimension,
-            'data': new_dim
-        }
-
     def remove_dimension(self, dim):
         inp_index = self.input_from_dim(dim)
         self.delete_input(inp_index)
@@ -71,21 +63,6 @@ def remove_dimension(self, dim):
         self.delete_output(out_index)
         self.delete_output(out_index)
         self.dims -= 1
-        # del self.actions[f'remove dimension {dim}']
-        self.rebuild_remove_actions()
-
-    def rebuild_remove_actions(self):
-
-        remove_keys = []
-        for k, v in self.actions.items():
-            if k.startswith('remove dimension'):
-                remove_keys.append(k)
-
-        for k in remove_keys:
-            del self.actions[k]
-
-        for i in range(1, self.dims):
-            self.actions[f'remove dimension {i+1}'] = {'method': self.remove_dimension, 'data': i+1}
 
     def input_from_dim(self, dim):
         return 1 + 2*(dim-1)
@@ -105,10 +82,10 @@ def iterate(self, dim):
         exec_out_index = self.output_from_dim(dim)
         data_out_index = exec_out_index+1
 
-        for i in range(self.input(inp_index),
-                       self.input(inp_index+1)):
+        for i in range(self.input(inp_index).payload,
+                       self.input(inp_index+1).payload):
 
-            self.set_output_val(data_out_index, i)
+            self.set_output_val(data_out_index, Data(i))
             self.exec_output(exec_out_index)
 
             if dim < self.dims:
@@ -117,19 +94,19 @@ def iterate(self, dim):
 
 class ForEachLoop_Node(CSNodeBase):
     title = 'for each'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.Integer(), label='elements'),
+        NodeInputType(type_='exec'),
+        NodeInputType(label='elements'),
     ]
     init_outputs = [
-        NodeOutputBP('loop', type_='exec'),
-        NodeOutputBP('e', type_='data'),
-        NodeOutputBP('finished', type_='exec'),
+        NodeOutputType('loop', type_='exec'),
+        NodeOutputType('e', type_='data'),
+        NodeOutputType('finished', type_='exec'),
     ]
+    GUI = guis.ForEachLoopGui
 
     def update_event(self, inp=-1):
-        for e in self.input(0):
+        for e in self.input(0).payload:
             self.set_output_val(1, e)
             self.exec_output(0)
 
@@ -138,18 +115,17 @@ def update_event(self, inp=-1):
 
 class WhileLoop_Node(CSNodeBase):
     title = 'while'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.Boolean(), label='cond'),
+        NodeInputType(type_='exec'),
+        NodeInputType(label='cond'),
     ]
     init_outputs = [
-        NodeOutputBP('loop', type_='exec'),
-        NodeOutputBP('finished', type_='exec'),
+        NodeOutputType('loop', type_='exec'),
+        NodeOutputType('finished', type_='exec'),
     ]
 
     def update_event(self, inp=-1):
-        while self.input(0):
+        while self.input(0).payload:
             self.exec_output(0)
 
         self.exec_output(1)
@@ -157,19 +133,18 @@ def update_event(self, inp=-1):
 
 class DoWhileLoop_Node(CSNodeBase):
     title = 'do while'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.Boolean(), label='cond'),
+        NodeInputType(type_='exec'),
+        NodeInputType(label='cond'),
     ]
     init_outputs = [
-        NodeOutputBP('loop', type_='exec'),
-        NodeOutputBP('finished', type_='exec'),
+        NodeOutputType('loop', type_='exec'),
+        NodeOutputType('finished', type_='exec'),
     ]
 
     def update_event(self, inp=-1):
         self.exec_output(0)
-        while self.input(0):
+        while self.input(0).payload:
             self.exec_output(0)
         self.exec_output(1)
 
diff --git a/ryven/example_nodes/std/gui.py b/ryven/example_nodes/std/gui.py
new file mode 100644
index 00000000..8d332bd3
--- /dev/null
+++ b/ryven/example_nodes/std/gui.py
@@ -0,0 +1,595 @@
+import re
+
+from ryven.gui_env import *
+
+from special_nodes import *
+
+from qtpy.QtGui import QFont
+from qtpy.QtCore import Qt, Signal, QEvent
+from qtpy.QtWidgets import QPushButton, QComboBox, QSlider, QTextEdit, QPlainTextEdit, QWidget, QVBoxLayout, QLineEdit, \
+    QDialog, QMessageBox
+
+
+"""
+    generic base classes
+"""
+
+
+class GuiBase(NodeGUI):
+    pass
+
+
+"""
+    operator nodes
+"""
+
+
+class OperatorNodeBaseGui(GuiBase):
+    input_widget_classes = {
+        'in': inp_widgets.Builder.evaled_line_edit(size='s', resizing=True),
+    }
+    # init_input_widgets = {
+    #     0: {'name': 'in', 'pos': 'besides'},
+    #     1: {'name': 'in', 'pos': 'besides'},
+    # }
+    style = 'small'
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['add input'] = {'method': self.add_operand_input}
+        self.actions['remove input'] = {}
+
+        for inp in self.node.inputs:
+            self.input_widgets[inp] = {'name': 'in', 'pos': 'besides'}
+
+    def initialized(self):
+        super().initialized()
+        self.rebuild_remove_actions()
+
+    def add_operand_input(self):
+        self.register_new_operand_input(self.node.num_inputs + 1)
+        self.node.add_op_input()
+
+    def register_new_operand_input(self, index):
+        self.actions[f'remove input'][f'{index}'] = {
+            'method': self.remove_operand_input,
+            'data': index
+        }
+
+    def remove_operand_input(self, index):
+        self.node.remove_op_input(index)
+        self.rebuild_remove_actions()
+
+    def rebuild_remove_actions(self):
+        self.actions['remove input'] = {}
+        for i in range(self.node.num_inputs):
+            self.actions[f'remove input'][f'{i}'] = \
+                {'method': self.remove_operand_input, 'data': i}
+
+
+class LogicNodeBaseGui(OperatorNodeBaseGui):
+    color = '#f58142'
+
+
+class ArithNodeBaseGui(OperatorNodeBaseGui):
+    color = '#58db53'
+
+
+class CompNodeBaseGui(OperatorNodeBaseGui):
+    color = '#a1574c'
+
+
+"""
+    control structures
+"""
+
+
+class CSNodeBaseGui(GuiBase):
+    style = 'normal'
+    color = '#b33a27'
+
+
+class ForLoopGui(CSNodeBaseGui):
+    input_widget_classes = {
+        'RangeFrom': inp_widgets.Builder.int_spinbox(0, (0, 1000000)),
+        'RangeTo': inp_widgets.Builder.int_spinbox(10, (0, 1000000)),
+    }
+    init_input_widgets = {
+        1: {'name': 'RangeFrom', 'pos': 'besides'},
+        2: {'name': 'RangeTo', 'pos': 'besides'},
+    }
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['add dimension'] = {'method': self.add_dimension}
+        self.actions['remove dimension'] = {}
+
+    def add_dimension(self):
+        self.attach_input_widgets(['RangeFrom', 'RangeTo'])
+        new_dim = self.node.dims + 1
+        self.node.add_dimension()
+
+        self.actions[f'remove dimension'][f'{new_dim}'] = {
+            'method': self.remove_dimension,
+            'data': new_dim
+        }
+
+    def remove_dimension(self, dim):
+        self.node.remove_dimension(dim)
+        self.rebuild_remove_actions()
+
+    def rebuild_remove_actions(self):
+        self.actions['remove dimension'] = {}
+        for i in range(1, self.dims):
+            self.actions[f'remove dimension'][f'{i + 1}'] = \
+                {'method': self.remove_dimension, 'data': i + 1}
+
+
+class ForEachLoopGui(CSNodeBaseGui):
+    input_widget_classes = {
+        'List': inp_widgets.Builder.evaled_line_edit(),
+    }
+    init_input_widgets = {
+        1: {'name': 'List', 'pos': 'besides'},
+    }
+
+
+"""
+    special nodes
+"""
+
+
+class SpecialNodeGuiBase(GuiBase):
+    color = '#FFCA00'
+
+
+class DualNodeBaseGui(SpecialNodeGuiBase):
+    def initialized(self):
+        super().initialized()
+        self.clean_actions()
+
+    def clean_actions(self):
+        if 'make passive' in self.actions:
+            del self.actions['make passive']
+        if 'make active' in self.actions:
+            del self.actions['make active']
+
+        if self.node.active:
+            self.actions['make passive'] = {'method': self.make_passive}
+        else:
+            self.actions['make active'] = {'method': self.make_active}
+
+    def make_passive(self):
+        del self.actions['make passive']
+        self.actions['make active'] = {'method': self.make_active}
+        self.node.make_passive()
+
+    def make_active(self):
+        del self.actions['make active']
+        self.actions['make passive'] = {'method': self.make_passive}
+        self.node.make_active()
+
+
+class CheckpointNodeGui(DualNodeBaseGui):
+    style = 'small'
+    display_title = ''
+
+    def initialized(self):
+        super().initialized()
+        self.actions['add output'] = {'method': self.add_output}
+        self.actions['remove output'] = {}
+        self.rebuild_remove_actions()
+
+    def add_output(self):
+        self.node.add_output()
+        self.rebuild_remove_actions()
+
+    def remove_output(self, index):
+        self.node.remove_output(index)
+        self.rebuild_remove_actions()
+
+    def rebuild_remove_actions(self):
+        self.actions['remove output'] = {}
+        for i in range(len(self.node.outputs)):
+            self.actions['remove output'][f'{i}'] = \
+                {'method': self.remove_output, 'data': i}
+
+
+class ButtonNode_MainWidget(QPushButton, NodeMainWidget):
+
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QPushButton.__init__(self)
+
+        self.clicked.connect(self.update_node)
+
+
+class ButtonNodeGui(SpecialNodeGuiBase):
+    main_widget_class = ButtonNode_MainWidget
+    main_widget_pos = 'between ports'
+    color = '#99dd55'
+
+
+class ClockNode_MainWidget(NodeMainWidget, QPushButton):
+
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QPushButton.__init__(self)
+
+        self.clicked.connect(self.node.toggle)
+
+
+class ClockNodeGui(SpecialNodeGuiBase):
+    main_widget_class = ClockNode_MainWidget
+    main_widget_pos = 'below ports'
+    input_widget_classes = {
+        'delay': inp_widgets.Builder.int_slider(1, (0, 10000)),
+        'iter': inp_widgets.Builder.int_spinbox(-1, (-1, 1000)),
+    }
+    init_input_widgets = {
+        0: {'name': 'delay', 'pos': 'besides'},
+        1: {'name': 'iter', 'pos': 'besides'},
+    }
+    color = '#5d95de'
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['start'] = {'method': self.start}
+        self.actions['stop'] = {'method': self.stop}
+
+    def start(self):
+        self.node.start()
+
+    def stop(self):
+        self.node.stop()
+
+
+class LogNodeGui(SpecialNodeGuiBase):
+    color = '#5d95de'
+
+
+class SliderNode_MainWidget(NodeMainWidget, QSlider):
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QSlider.__init__(self, Qt.Horizontal)
+
+        self.setRange(0, 1000)
+        self.setValue(self.node.val*1000)
+        self.valueChanged.connect(self.value_changed)
+
+    def value_changed(self, v):
+        self.node.val = v/1000
+        self.update_node()
+
+
+class SliderNodeGui(SpecialNodeGuiBase):
+    main_widget_class = SliderNode_MainWidget
+    main_widget_pos = 'below ports'
+    input_widget_classes = {
+        'scale': inp_widgets.Builder.int_spinbox(1, (1, 99)),
+        'round': inp_widgets.Builder.bool_checkbox(True),
+    }
+    init_input_widgets = {
+        0: {'name': 'scale', 'pos': 'besides'},
+        1: {'name': 'round', 'pos': 'besides'},
+    }
+
+    def initialized(self):
+        self.main_widget().setValue(self.node.val*1000)
+
+
+class DynamicPortsGui(SpecialNodeGuiBase):
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['add input'] = {'method': self.add_input}
+        self.actions['remove input'] = {}
+        self.actions['add output'] = {'method': self.add_output}
+        self.actions['remove output'] = {}
+
+    def add_input(self):
+        self.node.add_input()
+        index = len(self.node.inputs)-1
+        self.actions['remove input'][str(index)] = \
+            {'method': self.remove_input, 'data': index}
+
+    def remove_input(self, index: int):
+        self.node.remove_input(index)
+        del self.actions['remove input'][str(index)]
+
+    def add_output(self):
+        self.node.add_output()
+        index = len(self.node.outputs)-1
+        self.actions['remove output'][str(index)] = \
+            {'method': self.remove_output, 'data': index}
+
+    def remove_output(self, index: int):
+        self.node.remove_output(index)
+        del self.actions['remove output'][str(index)]
+
+
+class ExecNode_MainWidget(NodeMainWidget, QTextEdit):
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QTextEdit.__init__(self)
+
+        self.setFont(QFont('Consolas', 9))
+        self.textChanged.connect(self.text_changed)
+        self.setFixedHeight(150)
+        self.setFixedWidth(300)
+
+    def text_changed(self):
+        self.node.code = self.toPlainText()
+        self.update_node()
+
+    def get_state(self) -> dict:
+        return {
+            'text': self.toPlainText(),
+        }
+
+    def set_state(self, data: dict):
+        self.setPlainText(data['text'])
+
+
+class ExecNodeGui(DynamicPortsGui):
+    main_widget_class = ExecNode_MainWidget
+    main_widget_pos = 'between ports'
+
+
+class EvalNode_MainWidget(NodeMainWidget, QPlainTextEdit):
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QPlainTextEdit.__init__(self)
+
+        self.setFont(QFont('Consolas', 9))
+        self.textChanged.connect(self.text_changed)
+        self.setMaximumHeight(50)
+        self.setMaximumWidth(200)
+
+    def text_changed(self):
+        self.node.expression_code = self.toPlainText()
+        self.update_node()
+
+    def get_state(self) -> dict:
+        return {
+            'text': self.toPlainText(),
+        }
+
+    def set_state(self, data: dict):
+        self.setPlainText(data['text'])
+
+
+class EvalNodeGui(SpecialNodeGuiBase):
+    main_widget_class = EvalNode_MainWidget
+    main_widget_pos = 'between ports'
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['add input'] = {'method': self.add_input}
+        self.actions['remove input'] = {}
+
+    def add_input(self):
+        self.node.add_param_input()
+        index = self.node.param_inputs-1
+        self.actions['remove input'][str(index)] = \
+            {'method': self.remove_input, 'data': index}
+
+    def remove_input(self, index: int):
+        self.node.remove_param_input(index)
+        del self.actions['remove input'][str(index)]
+
+
+class InterpreterConsole(NodeMainWidget, QWidget):
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QWidget.__init__(self)
+
+        self.inp_line_edit = ConsoleInpLineEdit()
+        self.output_text_edit = ConsoleOutDisplay()
+
+        self.inp_line_edit.returned.connect(self.node.process_input)
+
+        self.setLayout(QVBoxLayout())
+        self.layout().addWidget(self.output_text_edit)
+        self.layout().addWidget(self.inp_line_edit)
+
+        self.last_hist_len = 0
+
+    def interp_updated(self):
+
+        if self.last_hist_len < len(self.node.hist):
+            self.output_text_edit.appendPlainText('\n'.join(self.node.hist[self.last_hist_len:]))
+        else:
+            self.output_text_edit.clear()
+            self.output_text_edit.setPlainText('\n'.join(self.node.hist))
+
+        self.last_hist_len = len(self.node.hist)
+
+
+class ConsoleInpLineEdit(QLineEdit):
+
+    returned = Signal(str)
+
+    def __init__(self):
+        super().__init__()
+
+        self.hist_index = 0
+        self.hist = []
+
+    def event(self, ev: QEvent) -> bool:
+
+        if ev.type() == QEvent.KeyPress:
+
+            if ev.key() == Qt.Key_Tab:
+                self.insert(' '*4)
+                return True
+
+            elif ev.key() == Qt.Key_Backtab:
+                ccp = self.cursorPosition()
+                text_left = self.text()[:ccp]
+                text_right = self.text()[ccp:]
+                ends_with_tab = re.match(r"(.*)\s\s\s\s$", text_left)
+                if ends_with_tab:
+                    self.setText(text_left[:-4]+text_right)
+                    self.setCursorPosition(ccp-4)
+                    return True
+
+            elif ev.key() == Qt.Key_Up:
+                self.recall(self.hist_index - 1)
+                return True
+
+            elif ev.key() == Qt.Key_Down:
+                self.recall(self.hist_index + 1)
+                return True
+
+            elif ev.key() == Qt.Key_Return:
+                self.return_key()
+                return True
+
+        return super().event(ev)
+
+    def return_key(self) -> None:
+        text = self.text()
+        for line in text.splitlines():
+            self.record(line)
+        self.returned.emit(text)
+        self.clear()
+
+    def record(self, line: str) -> None:
+        """store line in history buffer and update hist_index"""
+
+        self.hist.append(line)
+
+        if self.hist_index == len(self.hist)-1 or line != self.hist[self.hist_index]:
+            self.hist_index = len(self.hist)
+
+    def recall(self, index: int) -> None:
+        """select a line from the history list"""
+
+        if len(self.hist) > 0 and 0 <= index < len(self.hist):
+            self.setText(self.hist[index])
+            self.hist_index = index
+
+
+class ConsoleOutDisplay(QPlainTextEdit):
+    def __init__(self):
+        super().__init__()
+
+        self.setReadOnly(True)
+        self.setFont(QFont('Source Code Pro', 9))
+
+
+class InterpreterConsoleGui(SpecialNodeGuiBase):
+    main_widget_class = InterpreterConsole
+    main_widget_pos = 'between ports'
+
+
+class StorageNodeGui(SpecialNodeGuiBase):
+    color = '#aadd55'
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['clear'] = {'method': self.clear}
+
+    def clear(self):
+        self.node.clear()
+
+
+class LinkIN_NodeGui(SpecialNodeGuiBase):
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['add input'] = {'method': self.add_inp}
+        self.actions['remove inp'] = {}
+        self.actions['copy ID'] = {'method': self.copy_ID}
+
+    def copy_ID(self):
+        from qtpy.QtWidgets import QApplication
+        QApplication.clipboard().setText(str(self.node.ID))
+
+    def add_inp(self):
+        self.node.add_input()
+        index = self.node.inputs-1
+        self.actions['remove inp'][str(index)] = {
+            'method': self.remove_inp,
+            'data': index
+        }
+
+    def remove_inp(self, index: int):
+        self.node.remove_input(index)
+        del self.actions['remove inp'][str(index)]
+
+
+class LinkOUT_NodeGui(SpecialNodeGuiBase):
+
+    class IDInpDialog(QDialog):
+        def __init__(self):
+            super().__init__()
+            self.id_str = None
+            self.setLayout(QVBoxLayout())
+            self.line_edit = QLineEdit()
+            self.layout().addWidget(self.line_edit)
+            self.line_edit.returnPressed.connect(self.return_pressed)
+
+        def return_pressed(self):
+            self.id_str = self.line_edit.text()
+            self.accept()
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.actions['link to ID'] = {'method': self.link_to_ID}
+
+    def link_to_ID(self):
+        d = self.IDInpDialog()
+        d.exec_()
+
+        if d.id_str is not None:
+            n = LinkIN_Node.INSTANCES.get(d.id_str)
+            if n is not None:
+                self.node.link_to(n)
+            else:
+                QMessageBox.warning(
+                    self,
+                    title='link failed',
+                    text=f'No node with ID "{d.id_str}" found'
+                )
+
+
+"""
+    export
+"""
+
+
+export_guis([
+    DualNodeBaseGui,
+
+    CheckpointNodeGui,
+    OperatorNodeBaseGui,
+    LogicNodeBaseGui,
+    ArithNodeBaseGui,
+    CompNodeBaseGui,
+
+    CSNodeBaseGui,
+    ForLoopGui,
+    ForEachLoopGui,
+
+    SpecialNodeGuiBase,
+    ButtonNodeGui,
+    ClockNodeGui,
+    LogNodeGui,
+    SliderNodeGui,
+    DynamicPortsGui,
+    ExecNodeGui,
+    EvalNodeGui,
+    InterpreterConsoleGui,
+    StorageNodeGui,
+    LinkIN_NodeGui,
+    LinkOUT_NodeGui,
+])
+
diff --git a/ryven/example_nodes/std/nodes.py b/ryven/example_nodes/std/nodes.py
index d2b2a48d..71502242 100644
--- a/ryven/example_nodes/std/nodes.py
+++ b/ryven/example_nodes/std/nodes.py
@@ -1,5 +1,5 @@
-from ryven.NENV import *
-# widgets = import_widgets(__file__)
+from ryven.node_env import *
+# widgets = import_gui(__file__)
 
 import sys
 import os
@@ -9,8 +9,8 @@
 from basic_operators import nodes as operator_nodes
 from control_structures import nodes as cs_nodes
 
-export_nodes(
+export_nodes([
     *special_nodes,
     *operator_nodes,
     *cs_nodes,
-)
+])
diff --git a/ryven/example_nodes/std/special_nodes.py b/ryven/example_nodes/std/special_nodes.py
index 6de2843b..03f2f255 100644
--- a/ryven/example_nodes/std/special_nodes.py
+++ b/ryven/example_nodes/std/special_nodes.py
@@ -1,47 +1,41 @@
 import code
 from contextlib import redirect_stdout, redirect_stderr
+from packaging.version import Version
 
-from ryven.NENV import *
-widgets = import_widgets(__file__)
+from ryvencore.addons.Logging import LoggingAddon
+from ryven.node_env import *
+
+guis = import_guis(__file__)
 
 
 class NodeBase(Node):
-    version = 'v0.1'
-    color = '#FFCA00'
+    version = 'v0.2'
+    GUI = guis.SpecialNodeGuiBase
+
+    def have_gui(self):
+        return hasattr(self, 'gui')
 
 
 class DualNodeBase(NodeBase):
     """For nodes that can be active and passive"""
 
-    version = 'v0.1'
+    GUI = guis.DualNodeBaseGui
 
     def __init__(self, params, active=True):
         super().__init__(params)
 
         self.active = active
-        if active:
-            self.actions['make passive'] = {'method': self.make_passive}
-        else:
-            self.actions['make active'] = {'method': self.make_active}
 
     def make_passive(self):
-        del self.actions['make passive']
-
         self.delete_input(0)
         self.delete_output(0)
         self.active = False
 
-        self.actions['make active'] = {'method': self.make_active}
-
     def make_active(self):
-        del self.actions['make active']
-
         self.create_input(type_='exec', insert=0)
         self.create_output(type_='exec', insert=0)
         self.active = True
 
-        self.actions['make passive'] = {'method': self.make_passive}
-
     def get_state(self) -> dict:
         return {
             'active': self.active
@@ -51,41 +45,26 @@ def set_state(self, data: dict, version):
         self.active = data['active']
 
 
-# -------------------------------------------
-
-
-class Checkpoint_Node(NodeBase):
+class Checkpoint_Node(DualNodeBase):
     """Provides a simple checkpoint to reroute your connections"""
 
     title = 'checkpoint'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='data'),
+        NodeInputType(type_='data'),
     ]
     init_outputs = [
-        NodeOutputBP(type_='data'),
+        NodeOutputType(type_='data'),
     ]
-    style = 'small'
+    GUI = guis.CheckpointNodeGui
 
     def __init__(self, params):
         super().__init__(params)
 
-        self.display_title = ''
-
         self.active = False
 
-        # initial actions
-        self.actions['add output'] = {
-            'method': self.add_output
-        }
-        self.actions['remove output'] = {
-            '0': {'method': self.remove_output, 'data': 0}
-        }
-        self.actions['make active'] = {
-            'method': self.make_active
-        }
-
-    """State transitions"""
+    """
+    state transitions
+    """
 
     def clear_ports(self):
         # remove all outputs
@@ -97,60 +76,29 @@ def clear_ports(self):
             self.delete_input(0)
 
     def make_active(self):
-        self.active = True
-
-        # rebuild inputs and outputs
+        num_outputs = len(self.outputs)
         self.clear_ports()
-        self.create_input(type_='exec')
-        self.create_output(type_='exec')
+        super().make_active()
+        for i in range(1, num_outputs):
+            self.add_output()
 
-        # update actions
-        del self.actions['make active']
-        self.actions['make passive'] = {
-            'method': self.make_passive
-        }
-        self.actions['remove output'] = {
-            '0': {'method': self.remove_output, 'data': 0}
-        }
 
     def make_passive(self):
-        self.active = False
-
-        # rebuild inputs and outputs
+        num_outputs = len(self.outputs)
+        super().make_passive()
         self.clear_ports()
-        self.create_input(type_='data')
-        self.create_output(type_='data')
-
-        # update actions
-        del self.actions['make passive']
-        self.actions['make active'] = {
-            'method': self.make_active
-        }
-        self.actions['remove output'] = {
-            '0': {'method': self.remove_output, 'data': 0}
-        }
-
-    """Actions"""
+        for i in range(num_outputs):
+            self.add_output()
 
     def add_output(self):
-        index = len(self.outputs)
-
-        if self.active:
-            self.create_output(type_='exec')
-        else:
-            self.create_output(type_='data')
-
-        self.actions['remove output'][str(index)] = {
-            'method': self.remove_output,
-            'data': index,
-        }
+        self.create_output(type_='exec' if self.active else 'data')
 
     def remove_output(self, index):
         self.delete_output(index)
 
-        del self.actions['remove output'][str(len(self.outputs))]
-
-    """Behavior"""
+    """
+    update
+    """
 
     def update_event(self, inp=-1):
         if self.active and inp == 0:
@@ -162,36 +110,14 @@ def update_event(self, inp=-1):
             for i in range(len(self.outputs)):
                 self.set_output_val(i, data)
 
-    """State Reload"""
-
-    def get_state(self) -> dict:
-        return {
-            'active': self.active,
-            'num outputs': len(self.outputs),
-        }
-
-    def set_state(self, data: dict, version):
-        self.actions['remove output'] = {
-            {'method': self.remove_output, 'data': i}
-            for i in range(data['num outputs'])
-        }
-
-        if data['active']:
-            self.make_active()
-
 
 class Button_Node(NodeBase):
     title = 'Button'
-    version = 'v0.1'
-    main_widget_class = widgets.ButtonNode_MainWidget
-    main_widget_pos = 'between ports'
-    init_inputs = [
-
-    ]
+    init_inputs = []
     init_outputs = [
-        NodeOutputBP(type_='exec')
+        NodeOutputType(type_='exec')
     ]
-    color = '#99dd55'
+    GUI = guis.ButtonNodeGui
 
     def update_event(self, inp=-1):
         self.exec_output(0)
@@ -199,24 +125,20 @@ def update_event(self, inp=-1):
 
 class Print_Node(DualNodeBase):
     title = 'Print'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.Data(size='m')),
+        NodeInputType(type_='exec'),
+        NodeInputType(),
     ]
     init_outputs = [
-        NodeOutputBP(type_='exec'),
+        NodeOutputType(type_='exec'),
     ]
-    color = '#5d95de'
 
     def __init__(self, params):
         super().__init__(params, active=True)
 
     def update_event(self, inp=-1):
-        if self.active and inp == 0:
-            print(self.input(1))
-        elif not self.active:
-            print(self.input(0))
+        if inp == 0:
+            print(self.input(1 if self.active else 0).payload)
 
 
 import logging
@@ -224,131 +146,143 @@ def update_event(self, inp=-1):
 
 class Log_Node(DualNodeBase):
     title = 'Log'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP('msg', type_='data'),
+        NodeInputType(type_='exec'),
+        NodeInputType('msg', type_='data'),
     ]
     init_outputs = [
-        NodeOutputBP(type_='exec'),
+        NodeOutputType(type_='exec'),
     ]
-    main_widget_class = widgets.LogNode_MainWidget
-    main_widget_pos = 'below ports'
-    color = '#5d95de'
+    GUI = guis.LogNodeGui
+
+    logs = {}   # {int: Logger}
+    in_use = set()  # make sure we don't reuse numbers on copy & paste
 
     def __init__(self, params):
         super().__init__(params, active=True)
 
-        self.logger = self.new_logger('Log Node')
-
-        self.targets = {
-            **self.script.logs_manager.default_loggers,
-            'own': self.logger,
-        }
-        self.target = 'global'
+        self.number: int = None
+        self.logger: logging.Logger = None
 
-    def update_event(self, inp=-1):
-        if self.active and inp == 0:
-            i = 1
-        elif not self.active:
-            i = 0
-        else:
-            return
+    def place_event(self):
+        if self.number is None:
+            # didn't load; initialize
+            self.number = len(self.logs)
+            self.logs[self.number] = \
+                self.logs_ext().new_logger(self, 'Log Node')
+            self.in_use.add(self.number)
 
-        msg = self.input(i)
+    def logs_ext(self) -> LoggingAddon:
+        return self.get_addon('Logging')
 
-        self.targets[self.target].log(logging.INFO, msg=msg)
+    def update_event(self, inp=-1):
+        if inp == 0:
+            msg = self.input(1 if self.active and inp == 0 else 0).payload
+            self.logs[self.number].log(logging.INFO, msg=msg)
+            # TODO: support more than INFO, with a dropdown menu in main widget
 
     def get_state(self) -> dict:
         return {
             **super().get_state(),
-            'target': self.target,
+            'number': self.number,
         }
 
     def set_state(self, data: dict, version):
+        if Version(version) < Version('0.2'):
+            # ignore old version
+            return
         super().set_state(data, version)
-        self.target = data['target']
-        if self.session.gui and self.main_widget():
-            self.main_widget().set_target(self.target)
+        n = data['number']
+        if n not in self.in_use:
+            self.number = n
+        else:
+            # number already in use; generate a new one
+            # happens on copy & paste
+            self.number = len(self.logs)
+
+        # the logging addon will have re-created the logger
+        # for us already
+        self.logs[self.number] = \
+            self.logs_ext().new_logger(self, 'Log Node')
 
 
 class Clock_Node(NodeBase):
     title = 'clock'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(dtype=dtypes.Float(default=0.1), label='delay'),
-        NodeInputBP(dtype=dtypes.Integer(default=-1, bounds=(-1, 1000)), label='iterations'),
+        NodeInputType('delay'),
+        NodeInputType('iterations'),
     ]
     init_outputs = [
-        NodeOutputBP(type_='exec')
+        NodeOutputType(type_='exec')
     ]
-    color = '#5d95de'
-    main_widget_class = widgets.ClockNode_MainWidget
-    main_widget_pos = 'below ports'
+    GUI = guis.ClockNodeGui
+
+    # When running with GUI, this node uses QTime which doesn't
+    # block the GUI. When running without GUI, it uses time.sleep()
 
     def __init__(self, params):
         super().__init__(params)
 
-        self.actions['start'] = {'method': self.start}
-        self.actions['stop'] = {'method': self.stop}
+        self.running_with_qt = False
 
-        if self.session.gui:
+    def place_event(self):
+        self.running_with_qt = self.GUI is not None
 
+        if self.running_with_qt:
             from qtpy.QtCore import QTimer
-            self.timer = QTimer(self)
+            self.timer = QTimer()
             self.timer.timeout.connect(self.timeouted)
             self.iteration = 0
 
-
     def timeouted(self):
         self.exec_output(0)
         self.iteration += 1
-        if -1 < self.input(1) <= self.iteration:
+        if -1 < self.input(1).payload <= self.iteration:
             self.stop()
 
     def start(self):
-        if self.session.gui:
-            self.timer.setInterval(self.input(0)*1000)
+        if self.running_with_qt:
+            self.timer.setInterval(self.input(0).payload)
             self.timer.start()
         else:
             import time
-            for i in range(self.input(1)):
+            for i in range(self.input(1).payload):
                 self.exec_output(0)
-                time.sleep(self.input(0))
+                time.sleep(self.input(0).payload/1000)
 
     def stop(self):
+        assert self.running_with_qt
+        self.timer.stop()
         self.iteration = 0
-        if self.session.gui:
-            self.timer.stop()
 
     def toggle(self):
         # triggered from main widget
-        if self.session.gui:
+        if self.running_with_qt:
             if self.timer.isActive():
                 self.stop()
             else:
                 self.start()
+        # toggling is impossible when using time.sleep()
 
     def update_event(self, inp=-1):
-        if self.session.gui:
-            self.timer.setInterval(self.input(0)*1000)
+        if self.running_with_qt:
+            self.timer.setInterval(self.input(0).payload)
 
     def remove_event(self):
-        self.stop()
+        if self.running_with_qt:
+            self.stop()
 
 
 class Slider_Node(NodeBase):
     title = 'slider'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(dtype=dtypes.Integer(default=1), label='scl'),
-        NodeInputBP(dtype=dtypes.Boolean(default=False), label='round'),
+        NodeInputType('scl'),
+        NodeInputType('round'),
     ]
     init_outputs = [
-        NodeOutputBP(),
+        NodeOutputType(),
     ]
-    main_widget_class = widgets.SliderNode_MainWidget
-    main_widget_pos = 'below ports'
+    GUI = guis.SliderNodeGui
 
     def __init__(self, params):
         super().__init__(params)
@@ -358,98 +292,46 @@ def __init__(self, params):
     def place_event(self):
         self.update()
 
-    def view_place_event(self):
-        # when running in gui mode, the value might come from the input widget
-        self.update()
-
     def update_event(self, inp=-1):
-
-        v = self.input(0) * self.val
-        if self.input(1):
+        v = self.input(0).payload * self.val
+        if self.input(1).payload:
             v = round(v)
-
-        self.set_output_val(0, v)
+        self.set_output_val(0, Data(v))
 
     def get_state(self) -> dict:
-        return {
-            'val': self.val,
-        }
+        return {'val': self.val}
 
     def set_state(self, data: dict, version):
         self.val = data['val']
 
 
 class _DynamicPorts_Node(NodeBase):
-    version = 'v0.1'
     init_inputs = []
     init_outputs = []
+    GUI = guis.DynamicPortsGui
 
-    def __init__(self, params):
-        super().__init__(params)
-
-        self.actions['add input'] = {'method': self.add_inp}
-        self.actions['add output'] = {'method': self.add_out}
-
-        self.num_inputs = 0
-        self.num_outputs = 0
-
-    def add_inp(self):
+    def add_input(self):
         self.create_input()
 
-        index = self.num_inputs
-        self.actions[f'remove input {index}'] = {
-            'method': self.remove_inp,
-            'data': index
-        }
-
-        self.num_inputs += 1
-
-    def remove_inp(self, index):
+    def remove_input(self, index):
         self.delete_input(index)
-        self.num_inputs -= 1
-        del self.actions[f'remove input {self.num_inputs}']
 
-    def add_out(self):
+    def add_output(self):
         self.create_output()
 
-        index = self.num_outputs
-        self.actions[f'remove output {index}'] = {
-            'method': self.remove_out,
-            'data': index
-        }
-
-        self.num_outputs += 1
-
-    def remove_out(self, index):
+    def remove_output(self, index):
         self.delete_output(index)
-        self.num_outputs -= 1
-        del self.actions[f'remove output {self.num_outputs}']
-
-    def get_state(self) -> dict:
-        return {
-            'num inputs': self.num_inputs,
-            'num outputs': self.num_outputs,
-        }
-
-    def set_state(self, data: dict):
-        self.num_inputs = data['num inputs']
-        self.num_outputs = data['num outputs']
 
 
 class Exec_Node(_DynamicPorts_Node):
     title = 'exec'
-    version = 'v0.1'
-    main_widget_class = widgets.CodeNode_MainWidget
-    main_widget_pos = 'between ports'
+    GUI = guis.ExecNodeGui
 
     def __init__(self, params):
         super().__init__(params)
 
         self.code = None
 
-    def place_event(self):
-        pass
-
     def update_event(self, inp=-1):
         exec(self.code)
 
@@ -466,21 +348,17 @@ def set_state(self, data: dict, version):
 
 class Eval_Node(NodeBase):
     title = 'eval'
-    version = 'v0.1'
     init_inputs = [
-        # NodeInputBP(),
+        # NodeInputType(),
     ]
     init_outputs = [
-        NodeOutputBP(),
+        NodeOutputType(),
     ]
-    main_widget_class = widgets.EvalNode_MainWidget
-    main_widget_pos = 'between ports'
+    GUI = guis.EvalNodeGui
 
     def __init__(self, params):
         super().__init__(params)
 
-        self.actions['add input'] = {'method': self.add_param_input}
-
         self.number_param_inputs = 0
         self.expression_code = None
 
@@ -491,22 +369,18 @@ def place_event(self):
     def add_param_input(self):
         self.create_input()
 
-        index = self.number_param_inputs
-        self.actions[f'remove input {index}'] = {
-            'method': self.remove_param_input,
-            'data': index
-        }
-
         self.number_param_inputs += 1
 
     def remove_param_input(self, index):
         self.delete_input(index)
         self.number_param_inputs -= 1
-        del self.actions[f'remove input {self.number_param_inputs}']
 
     def update_event(self, inp=-1):
-        inp = [self.input(i) for i in range(self.number_param_inputs)]
-        self.set_output_val(0, eval(self.expression_code))
+        inp = [
+            self.input(i).payload if self.input(i) is not None else None
+            for i in range(self.number_param_inputs)
+        ]
+        self.set_output_val(0, Data(eval(self.expression_code)))
 
     def get_state(self) -> dict:
         return {
@@ -520,15 +394,18 @@ def set_state(self, data: dict, version):
 
 
 class Interpreter_Node(NodeBase):
-    """Provides a python interpreter via a basic console with access to the
-    node's properties."""
+    """
+    Provides a python interpreter via a basic console with access to the
+    node's properties.
+    """
     title = 'interpreter'
-    version = 'v0.1'
     init_inputs = []
     init_outputs = []
-    main_widget_class = widgets.InterpreterConsole
+    GUI = guis.InterpreterConsoleGui
 
-    # DEFAULT COMMANDS
+    """
+    commands
+    """
 
     def clear(self):
         self.hist.clear()
@@ -542,6 +419,10 @@ def reset(self):
         'reset': reset,
     }
 
+    """
+    behaviour
+    """
+
     def __init__(self, params):
         super().__init__(params)
 
@@ -552,8 +433,8 @@ def __init__(self, params):
         self.reset()
 
     def _hist_updated(self):
-        if self.session.gui:
-            self.main_widget().interp_updated()
+        if self.have_gui():
+            self.gui.main_widget().interp_updated()
 
     def process_input(self, cmds: str):
         m = self.COMMANDS.get(cmds)
@@ -582,33 +463,32 @@ def write(self, line: str):
 
 
 class Storage_Node(NodeBase):
-    """Sequentially stores all the data provided at the input in an array.
-    A COPY of the storage array is provided at the output"""
+    """
+    Sequentially stores all the data provided at the input in an array.
+    A shallow copy of the storage array is provided at the output
+    """
 
     title = 'store'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(),
+        NodeInputType(),
     ]
     init_outputs = [
-        NodeOutputBP(),
+        NodeOutputType(),
     ]
-    color = '#aadd55'
+    GUI = guis.StorageNodeGui
 
     def __init__(self, params):
         super().__init__(params)
 
         self.storage = []
 
-        self.actions['clear'] = {'method': self.clear}
-
     def clear(self):
         self.storage.clear()
-        self.set_output_val(0, [])
+        self.set_output_val(0, Data([]))
 
     def update_event(self, inp=-1):
-        self.storage.append(self.input(0))
-        self.set_output_val(0, self.storage.copy())
+        self.storage.append(self.input(0).payload)
+        self.set_output_val(0, Data(self.storage.copy()))
 
     def get_state(self) -> dict:
         return {
@@ -623,60 +503,42 @@ def set_state(self, data: dict, version):
 
 
 class LinkIN_Node(NodeBase):
-    """You can use link OUT nodes to link them up to this node.
+    """
     Whenever a link IN node receives data (or an execution signal),
-    if there is a linked OUT node, it will receive the data
-    and propagate it further."""
+    if there is a linked LinkOUT node, it will receive the data
+    and propagate it further.
+    Notice that this breaks the data flow, which can have substantial
+    performance implications and is generally not recommended.
+    """
 
     title = 'link IN'
-    version = 'v0.1'
     init_inputs = [
-        NodeInputBP(),
+        NodeInputType(),
     ]
     init_outputs = []  # no outputs
+    GUI = guis.LinkIN_NodeGui
 
     # instances registration
     INSTANCES = {}  # {UUID: node}
 
     def __init__(self, params):
         super().__init__(params)
-        self.display_title = 'link'
 
         # register
         self.ID: uuid.UUID = uuid.uuid4()
         self.INSTANCES[str(self.ID)] = self
 
-        self.actions['add input'] = {
-            'method': self.add_inp
-        }
-        self.actions['remove inp'] = {}
-        self.actions['copy ID'] = {
-            'method': self.copy_ID
-        }
-
         self.linked_node: LinkOUT_Node = None
 
-    def copy_ID(self):
-        from qtpy.QtWidgets import QApplication
-        QApplication.clipboard().setText(str(self.ID))
-
-    def add_inp(self):
-        index = len(self.inputs)
-
+    def add_input(self):
         self.create_input()
-
-        self.actions['remove inp'][str(index)] = {
-            'method': self.rem_inp,
-            'data': index,
-        }
         if self.linked_node is not None:
-            self.linked_node.add_out()
+            self.linked_node.add_output()
 
-    def rem_inp(self, index):
+    def remove_input(self, index):
         self.delete_input(index)
-        del self.actions['remove inp'][str(len(self.inputs))]
         if self.linked_node is not None:
-            self.linked_node.rem_out(index)
+            self.linked_node.remove_output(index)
 
     def update_event(self, inp=-1):
         if self.linked_node is not None:
@@ -713,9 +575,9 @@ class LinkOUT_Node(NodeBase):
     """The complement to the link IN node"""
 
     title = 'link OUT'
-    version = 'v0.1'
     init_inputs = []  # no inputs
     init_outputs = []  # will be synchronized with linked IN node
+    GUI = guis.LinkOUT_NodeGui
 
     INSTANCES = []
     PENDING_LINK_BUILDS = {}
@@ -731,43 +593,10 @@ def new_link_in_loaded(cls, n: LinkIN_Node):
 
     def __init__(self, params):
         super().__init__(params)
-        self.display_title = 'link'
 
         self.INSTANCES.append(self)
         self.linked_node: LinkIN_Node = None
 
-        self.actions['link to ID'] = {
-            'method': self.choose_link_ID
-        }
-
-    def choose_link_ID(self):
-        """opens a small input dialog for providing a copied link IN ID"""
-
-        from qtpy.QtWidgets import QDialog, QMessageBox, QVBoxLayout, QLineEdit
-
-        class IDInpDialog(QDialog):
-            def __init__(self):
-                super().__init__()
-                self.id_str = None
-                self.setLayout(QVBoxLayout())
-                self.line_edit = QLineEdit()
-                self.layout().addWidget(self.line_edit)
-                self.line_edit.returnPressed.connect(self.return_pressed)
-
-            def return_pressed(self):
-                self.id_str = self.line_edit.text()
-                self.accept()
-
-        d = IDInpDialog()
-        d.exec_()
-
-        if d.id_str is not None:
-            n = LinkIN_Node.INSTANCES.get(d.id_str)
-            if n is None:
-                QMessageBox.warning(title='link failed', text='couldn\'t find a valid link in node')
-            else:
-                self.link_to(n)
-
     def link_to(self, n: LinkIN_Node):
         self.linked_node = n
         n.linked_node = self
@@ -785,11 +614,11 @@ def link_to(self, n: LinkIN_Node):
 
         self.update()
 
-    def add_out(self):
+    def add_output(self):
         # triggered by linked_node
         self.create_output()
 
-    def rem_out(self, index):
+    def remove_output(self, index):
         # triggered by linked_node
         self.delete_output(index)
 
@@ -827,9 +656,6 @@ def remove_event(self):
             self.linked_node = None
 
 
-# -------------------------------------------
-
-
 nodes = [
     Checkpoint_Node,
     Button_Node,
diff --git a/ryven/example_nodes/std/widgets.py b/ryven/example_nodes/std/widgets.py
deleted file mode 100644
index 629926d1..00000000
--- a/ryven/example_nodes/std/widgets.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import re
-
-from ryven.NWENV import *
-
-from qtpy.QtGui import QFont
-from qtpy.QtCore import Qt, Signal, QEvent
-from qtpy.QtWidgets import QPushButton, QComboBox, QSlider, QTextEdit, QPlainTextEdit, QWidget, QVBoxLayout, QLineEdit
-
-
-class ButtonNode_MainWidget(QPushButton, MWB):
-
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QPushButton.__init__(self)
-
-        self.clicked.connect(self.update_node)
-
-
-class ClockNode_MainWidget(MWB, QPushButton):
-
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QPushButton.__init__(self)
-
-        self.clicked.connect(self.node.toggle)
-
-
-class LogNode_MainWidget(MWB, QComboBox):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QComboBox.__init__(self)
-
-        self.addItems(self.node.targets)
-
-        self.currentTextChanged.connect(self.text_changed)
-        self.set_target(self.node.target)
-
-    def text_changed(self, t):
-        self.node.target = t
-
-    def set_target(self, t):
-        self.setCurrentText(t)
-
-
-class SliderNode_MainWidget(MWB, QSlider):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QSlider.__init__(self, Qt.Horizontal)
-
-        self.setRange(0, 1000)
-        self.valueChanged.connect(self.value_changed)
-
-    def value_changed(self, v):
-        self.node.val = v/1000
-        self.update_node()
-
-    def get_state(self) -> dict:
-        return {
-            'val': self.value(),
-        }
-
-    def set_state(self, data: dict):
-        self.setValue(data['val'])
-
-
-class CodeNode_MainWidget(MWB, QTextEdit):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QTextEdit.__init__(self)
-
-        self.setFont(QFont('Consolas', 9))
-        self.textChanged.connect(self.text_changed)
-        self.setFixedHeight(150)
-        self.setFixedWidth(300)
-
-    def text_changed(self):
-        self.node.code = self.toPlainText()
-        self.update_node()
-
-    def get_state(self) -> dict:
-        return {
-            'text': self.toPlainText(),
-        }
-
-    def set_state(self, data: dict):
-        self.setPlainText(data['text'])
-
-
-class EvalNode_MainWidget(MWB, QPlainTextEdit):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QPlainTextEdit.__init__(self)
-
-        self.setFont(QFont('Consolas', 9))
-        self.textChanged.connect(self.text_changed)
-        self.setMaximumHeight(50)
-        self.setMaximumWidth(200)
-
-    def text_changed(self):
-        self.node.expression_code = self.toPlainText()
-        self.update_node()
-
-    def get_state(self) -> dict:
-        return {
-            'text': self.toPlainText(),
-        }
-
-    def set_state(self, data: dict):
-        self.setPlainText(data['text'])
-
-
-class InterpreterConsole(MWB, QWidget):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QWidget.__init__(self)
-
-        self.inp_line_edit = ConsoleInpLineEdit()
-        self.output_text_edit = ConsoleOutDisplay()
-
-        self.inp_line_edit.returned.connect(self.node.process_input)
-
-        self.setLayout(QVBoxLayout())
-        self.layout().addWidget(self.output_text_edit)
-        self.layout().addWidget(self.inp_line_edit)
-
-        self.last_hist_len = 0
-
-    def interp_updated(self):
-
-        if self.last_hist_len < len(self.node.hist):
-            self.output_text_edit.appendPlainText('\n'.join(self.node.hist[self.last_hist_len:]))
-        else:
-            self.output_text_edit.clear()
-            self.output_text_edit.setPlainText('\n'.join(self.node.hist))
-
-        self.last_hist_len = len(self.node.hist)
-
-
-class ConsoleInpLineEdit(QLineEdit):
-
-    returned = Signal(str)
-
-    def __init__(self):
-        super().__init__()
-
-        self.hist_index = 0
-        self.hist = []
-
-    def event(self, ev: QEvent) -> bool:
-
-        if ev.type() == QEvent.KeyPress:
-
-            if ev.key() == Qt.Key_Tab:
-                self.insert(' '*4)
-                return True
-
-            elif ev.key() == Qt.Key_Backtab:
-                ccp = self.cursorPosition()
-                text_left = self.text()[:ccp]
-                text_right = self.text()[ccp:]
-                ends_with_tab = re.match(r"(.*)\s\s\s\s$", text_left)
-                if ends_with_tab:
-                    self.setText(text_left[:-4]+text_right)
-                    self.setCursorPosition(ccp-4)
-                    return True
-
-            elif ev.key() == Qt.Key_Up:
-                self.recall(self.hist_index - 1)
-                return True
-
-            elif ev.key() == Qt.Key_Down:
-                self.recall(self.hist_index + 1)
-                return True
-
-            elif ev.key() == Qt.Key_Return:
-                self.return_key()
-                return True
-
-        return super().event(ev)
-
-    def return_key(self) -> None:
-        text = self.text()
-        for line in text.splitlines():
-            self.record(line)
-        self.returned.emit(text)
-        self.clear()
-
-    def record(self, line: str) -> None:
-        """store line in history buffer and update hist_index"""
-
-        self.hist.append(line)
-
-        if self.hist_index == len(self.hist)-1 or line != self.hist[self.hist_index]:
-            self.hist_index = len(self.hist)
-
-    def recall(self, index: int) -> None:
-        """select a line from the history list"""
-
-        if len(self.hist) > 0 and 0 <= index < len(self.hist):
-            self.setText(self.hist[index])
-            self.hist_index = index
-
-
-class ConsoleOutDisplay(QPlainTextEdit):
-    def __init__(self):
-        super().__init__()
-
-        self.setReadOnly(True)
-        self.setFont(QFont('Source Code Pro', 9))
-
-
-export_widgets(
-    ButtonNode_MainWidget,
-    ClockNode_MainWidget,
-    LogNode_MainWidget,
-    SliderNode_MainWidget,
-    CodeNode_MainWidget,
-    EvalNode_MainWidget,
-    InterpreterConsole,
-)
diff --git a/ryven/examples_projects/Portal2Highscore.png b/ryven/examples_projects/Portal2Highscore.png
deleted file mode 100644
index 5eb5411f..00000000
Binary files a/ryven/examples_projects/Portal2Highscore.png and /dev/null differ
diff --git a/ryven/examples_projects/Portal2Highscore_large.jpg b/ryven/examples_projects/Portal2Highscore_large.jpg
deleted file mode 100644
index 79dfd93d..00000000
Binary files a/ryven/examples_projects/Portal2Highscore_large.jpg and /dev/null differ
diff --git a/ryven/examples_projects/basics.json b/ryven/examples_projects/basics.json
index d658ca84..14c3f347 100644
--- a/ryven/examples_projects/basics.json
+++ b/ryven/examples_projects/basics.json
@@ -1,647 +1,1018 @@
 {
     "general info": {
-        "type": "Ryven project file"
+        "type": "Ryven project file",
+        "ryven version": "3.3.0a1"
     },
     "required packages": [
         {
             "name": "std",
-            "dir": "C:\\Users\\nutri\\projects\\ryven projects\\Ryven_mmorys\\Ryven\\ryven\\example_nodes\\std"
+            "dir": "/home/leon/projects/ryven_projects/Ryven/ryven/example_nodes/std"
         }
     ],
-    "scripts": [
-        {
-            "title": "hello world",
-            "variables": {
-                "a": {
-                    "serialized": "gASVMQEAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxlLg=="
+    "GID": 2,
+    "version": "0.4.0a12",
+    "flows": {
+        "hello world": {
+            "GID": 5,
+            "algorithm mode": "data",
+            "nodes": [
+                {
+                    "GID": 6,
+                    "version": "v0.2",
+                    "identifier": "std.Storage_Node",
+                    "state data": "gASVUggAAAAAAAB9lIwEZGF0YZRdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lcy4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 9,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 10,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 475.4795814751692,
+                    "pos y": 264.55248052403067,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "clear": {
+                            "method": "clear"
+                        }
+                    },
+                    "display title": "store"
                 },
-                "ctr": {
-                    "serialized": "gASVBAAAAAAAAABNPgwu"
-                }
-            },
-            "flow": {
-                "algorithm mode": "data",
-                "nodes": [
-                    {
-                        "identifier": "std.Storage_Node",
-                        "version": null,
-                        "state data": "gASVOwEAAAAAAAB9lIwEZGF0YZRdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxlcy4=",
-                        "additional data": {
-                            "special actions": {
-                                "clear": {
-                                    "method": "clear"
-                                }
-                            },
-                            "display title": "store"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 6,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 7
-                            }
-                        ],
-                        "GID": 5,
-                        "pos x": 475.4795814751692,
-                        "pos y": 264.55248052403067,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Eval_Node",
-                        "version": null,
-                        "state data": "gASVOQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMCmlucFswXVstMV2UdS4=",
-                        "additional data": {
-                            "special actions": {
-                                "add input": {
-                                    "method": "add_param_input"
-                                },
-                                "remove input 0": {
-                                    "method": "remove_param_input",
-                                    "data": 0
-                                }
+                {
+                    "GID": 13,
+                    "version": "v0.2",
+                    "identifier": "std.Eval_Node",
+                    "state data": "gASVOQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMCmlucFswXVstMV2UdS4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 15,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 16,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 462.86363383615935,
+                    "pos y": 370.17323807221123,
+                    "main widget data": "gASVGAAAAAAAAAB9lIwEdGV4dJSMCmlucFswXVstMV2Ucy4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "add input": {
+                            "method": "add_input"
+                        },
+                        "remove input": {}
+                    },
+                    "display title": "eval"
+                },
+                {
+                    "GID": 17,
+                    "version": "v0.2",
+                    "identifier": "built_in.Result_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 19,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 796.5680046166863,
+                    "pos y": 369.6301042263301,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "result"
+                },
+                {
+                    "GID": 20,
+                    "version": "v0.2",
+                    "identifier": "std.Slider_Node",
+                    "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/6FocrAgxJ3Mu",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 24,
+                            "type": "data",
+                            "label": "scl",
+                            "default": {
+                                "GID": 27,
+                                "identifier": "Data",
+                                "serialized": "gARLAS4="
                             },
-                            "display title": "eval"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 9,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 10
-                            }
-                        ],
-                        "GID": 8,
-                        "pos x": 462.86363383615935,
-                        "pos y": 370.17323807221123,
-                        "main widget data": "gASVGAAAAAAAAAB9lIwEdGV4dJSMCmlucFswXVstMV2Ucy4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.Result_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "result"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 12,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [],
-                        "GID": 11,
-                        "pos x": 796.5680046166863,
-                        "pos y": 369.6301042263301,
-                        "main widget data": "gAR9lC4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Slider_Node",
-                        "version": null,
-                        "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/4Q5WBBiTdXMu",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "slider"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "scl",
-                                "GID": 14,
-                                "val": "gARLAi4=",
-                                "dtype": "DType.Integer",
-                                "dtype state": "gASVLAAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAIwDZG9jlIwAlIwGYm91bmRzlE51Lg==",
-                                "has widget": true,
-                                "widget data": "gASVDAAAAAAAAAB9lIwDdmFslEsCcy4="
+                            "has widget": true,
+                            "widget name": "scale",
+                            "widget pos": "besides",
+                            "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTRsSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+                        },
+                        {
+                            "GID": 25,
+                            "type": "data",
+                            "label": "round",
+                            "default": {
+                                "GID": 123,
+                                "identifier": "Data",
+                                "serialized": "gASJLg=="
                             },
-                            {
-                                "type": "data",
-                                "label": "round",
-                                "GID": 15,
-                                "val": "gAROLg==",
-                                "dtype": "DType.Boolean",
-                                "dtype state": "gASVKgAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROdS4=",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwHY2hlY2tlZJSJcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 16
-                            }
-                        ],
-                        "GID": 13,
-                        "pos x": 273.19774517706185,
-                        "pos y": 503.4372312306748,
-                        "main widget data": "gASVDQAAAAAAAAB9lIwDdmFslE0VAnMu",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.Result_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "result"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 18,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [],
-                        "GID": 17,
-                        "pos x": 571.2515010942456,
-                        "pos y": 555.0662156323955,
-                        "main widget data": "gAR9lC4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.SetVar_Node",
-                        "version": null,
-                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
-                        "additional data": {
-                            "special actions": {
-                                "make active": {
-                                    "method": "action_make_active"
-                                }
+                            "has widget": true,
+                            "widget name": "round",
+                            "widget pos": "besides",
+                            "widget data": "gASVaQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTRwSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUiXVicy4="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 26,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 273.19774517706185,
+                    "pos y": 503.4372312306748,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "slider"
+                },
+                {
+                    "GID": 29,
+                    "version": "v0.2",
+                    "identifier": "built_in.Result_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 31,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 571.2515010942456,
+                    "pos y": 555.0662156323955,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "result"
+                },
+                {
+                    "GID": 32,
+                    "version": "v0.1",
+                    "identifier": "built_in.SetVar_Node",
+                    "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 38,
+                            "type": "data",
+                            "label": "var",
+                            "default": {
+                                "GID": 3801,
+                                "identifier": "Data",
+                                "serialized": "gASVBQAAAAAAAACMAWGULg=="
                             },
-                            "display title": "set var"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "var",
-                                "GID": 20,
-                                "val": "gASVBQAAAAAAAACMAWGULg==",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMAWGUcy4="
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTR0SjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
+                        },
+                        {
+                            "GID": 39,
+                            "type": "data",
+                            "label": "val",
+                            "default": {
+                                "GID": 4628,
+                                "identifier": "Data",
+                                "serialized": "gASVjRAAAAAAAABYhhAAAFszMDM1LCAzMDM2LCAzMDM3LCAzMDM4LCAzMDM5LCAzMDQwLCAzMDQxLCAzMDQyLCAzMDQzLCAzMDQ0LCAzMDQ1LCAzMDQ2LCAzMDQ3LCAzMDQ4LCAzMDQ5LCAzMDUwLCAzMDUxLCAzMDUyLCAzMDUzLCAzMDU0LCAzMDU1LCAzMDU2LCAzMDU3LCAzMDU4LCAzMDU5LCAzMDYwLCAzMDYxLCAzMDYyLCAzMDYzLCAzMDY0LCAzMDY1LCAzMDY2LCAzMDY3LCAzMDY4LCAzMDY5LCAzMDcwLCAzMDcxLCAzMDcyLCAzMDczLCAzMDc0LCAzMDc1LCAzMDc2LCAzMDc3LCAzMDc4LCAzMDc5LCAzMDgwLCAzMDgxLCAzMDgyLCAzMDgzLCAzMDg0LCAzMDg1LCAzMDg2LCAzMDg3LCAzMDg4LCAzMDg5LCAzMDkwLCAzMDkxLCAzMDkyLCAzMDkzLCAzMDk0LCAzMDk1LCAzMDk2LCAzMDk3LCAzMDk4LCAzMDk5LCAzMTAwLCAzMTAxLCAzMTAyLCAzMTAzLCAzMTA0LCAzMTA1LCAzMTA2LCAzMTA3LCAzMTA4LCAzMTA5LCAzMTEwLCAzMTExLCAzMTEyLCAzMTEzLCAzMTE0LCAzMTE1LCAzMTE2LCAzMTE3LCAzMTE4LCAzMTE5LCAzMTIwLCAzMTIxLCAzMTIyLCAzMTIzLCAzMTI0LCAzMTI1LCAzMTI2LCAzMTI3LCAzMTI4LCAzMTI5LCAzMTMwLCAzMTMxLCAzMTMyLCAzMTMzLCAzMTM0LCAzMTM0LCAzMTM1LCAzMTM2LCAzMTM3LCAzMTM4LCAzMTM5LCAzMTQwLCAzMTQxLCAzMTQyLCAzMTQzLCAzMTQ0LCAzMTQ1LCAzMTQ2LCAzMTQ3LCAzMTQ4LCAzMTQ5LCAzMTUwLCAzMTUxLCAzMTUyLCAzMTUzLCAzMTU0LCAzMTU1LCAzMTU2LCAzMTU3LCAzMTU4LCAzMTU5LCAzMTYwLCAzMTYxLCAzMTYyLCAzMTYzLCAzMTY0LCAzMTY1LCAzMTY2LCAzMTY3LCAzMTY4LCAzMTY5LCAzMTcwLCAzMTcxLCAzMTcyLCAzMTczLCAzMTc0LCAzMTc1LCAzMTc2LCAzMTc3LCAzMTc4LCAzMTc5LCAzMTgwLCAzMTgxLCAzMTgyLCAzMTgzLCAzMTg0LCAzMTg1LCAzMTg2LCAzMTg3LCAzMTg4LCAzMTg5LCAzMTkwLCAzMTkxLCAzMTkyLCAzMTkzLCAzMTk0LCAzMTk1LCAzMTk2LCAzMTk3LCAzMTk4LCAzMTk5LCAzMjAwLCAzMjAxLCAzMjAyLCAzMjAzLCAzMjA0LCAzMjA1LCAzMjA2LCAzMjA3LCAzMjA4LCAzMjA5LCAzMjEwLCAzMjExLCAzMjEyLCAzMjEzLCAzMjE0LCAzMjE1LCAzMjE2LCAzMjE3LCAzMjE4LCAzMjE5LCAzMjIwLCAzMjIxLCAzMjIyLCAzMjIzLCAzMjI0LCAzMjI1LCAzMjI2LCAzMjI3LCAzMjI4LCAzMjI5LCAzMjMwLCAzMjMxLCAzMjMyLCAzMjMzLCAzMjM0LCAzMjM1LCAzMjM2LCAzMjM3LCAzMjM4LCAzMjM5LCAzMjQwLCAzMjQxLCAzMjQyLCAzMjQzLCAzMjQ0LCAzMjQ1LCAzMjQ2LCAzMjQ3LCAzMjQ4LCAzMjQ5LCAzMjUwLCAzMjUxLCAzMjUyLCAzMjUzLCAzMjU0LCAzMjU1LCAzMjU2LCAzMjU3LCAzMjU4LCAzMjU5LCAzMjYwLCAzMjYxLCAzMjYyLCAzMjYzLCAzMjY0LCAzMjY1LCAzMjY2LCAzMjY3LCAzMjY4LCAzMjY5LCAzMjcwLCAzMjcxLCAzMjcyLCAzMjczLCAzMjc0LCAzMjc1LCAzMjc2LCAzMjc3LCAzMjc4LCAzMjc5LCAzMjgwLCAzMjgxLCAzMjgyLCAzMjgzLCAzMjg0LCAzMjg1LCAzMjg2LCAzMjg3LCAzMjg4LCAzMjg5LCAzMjkwLCAzMjkxLCAzMjkyLCAzMjkzLCAzMjk0LCAzMjk1LCAzMjk2LCAzMjk3LCAzMjk4LCAzMjk5LCAzMzAwLCAzMzAxLCAzMzAyLCAzMzAzLCAzMzA0LCAzMzA1LCAzMzA2LCAzMzA3LCAzMzA4LCAzMzA5LCAzMzEwLCAzMzExLCAzMzEyLCAzMzEzLCAzMzE0LCAzMzE1LCAzMzE2LCAzMzE3LCAzMzE4LCAzMzE5LCAzMzIwLCAzMzIxLCAzMzIyLCAzMzIzLCAzMzI0LCAzMzI1LCAzMzI2LCAzMzI3LCAzMzI4LCAzMzI5LCAzMzMwLCAzMzMxLCAzMzMyLCAzMzMzLCAzMzM0LCAzMzM1LCAzMzM2LCAzMzM3LCAzMzM4LCAzMzM5LCAzMzQwLCAzMzQxLCAzMzQyLCAzMzQzLCAzMzQ0LCAzMzQ1LCAzMzQ2LCAzMzQ3LCAzMzQ4LCAzMzQ5LCAzMzUwLCAzMzUxLCAzMzUyLCAzMzUzLCAzMzU0LCAzMzU1LCAzMzU2LCAzMzU3LCAzMzU4LCAzMzU5LCAzMzYwLCAzMzYxLCAzMzYyLCAzMzYzLCAzMzY0LCAzMzY1LCAzMzY2LCAzMzY3LCAzMzY4LCAzMzY5LCAzMzcwLCAzMzcxLCAzMzcyLCAzMzczLCAzMzc0LCAzMzc1LCAzMzc2LCAzMzc3LCAzMzc4LCAzMzc5LCAzMzgwLCAzMzgxLCAzMzgyLCAzMzgzLCAzMzg0LCAzMzg1LCAzMzg2LCAzMzg3LCAzMzg4LCAzMzg5LCAzMzkwLCAzMzkxLCAzMzkyLCAzMzkzLCAzMzk0LCAzMzk1LCAzMzk2LCAzMzk3LCAzMzk4LCAzMzk5LCAzNDAwLCAzNDAxLCAzNDAyLCAzNDAzLCAzNDA0LCAzNDA1LCAzNDA2LCAzNDA3LCAzNDA4LCAzNDA5LCAzNDEwLCAzNDExLCAzNDEyLCAzNDEzLCAzNDE0LCAzNDE1LCAzNDE2LCAzNDE3LCAzNDE4LCAzNDE5LCAzNDIwLCAzNDIxLCAzNDIyLCAzNDIzLCAzNDI0LCAzNDI1LCAzNDI2LCAzNDI3LCAzNDI4LCAzNDI5LCAzNDMwLCAzNDMxLCAzNDMyLCAzNDMzLCAzNDM0LCAzNDM1LCAzNDM2LCAzNDM3LCAzNDM4LCAzNDM5LCAzNDQwLCAzNDQxLCAzNDQyLCAzNDQzLCAzNDQ0LCAzNDQ1LCAzNDQ2LCAzNDQ3LCAzNDQ4LCAzNDQ5LCAzNDUwLCAzNDUxLCAzNDUyLCAzNDUzLCAzNDU0LCAzNDU1LCAzNDU2LCAzNDU3LCAzNDU4LCAzNDU5LCAzNDYwLCAzNDYxLCAzNDYyLCAzNDYzLCAzNDY0LCAzNDY1LCAzNDY2LCAzNDY3LCAzNDY4LCAzNDY5LCAzNDcwLCAzNDcxLCAzNDcyLCAzNDczLCAzNDc0LCAzNDc1LCAzNDc2LCAzNDc3LCAzNDc4LCAzNDc5LCAzNDgwLCAzNDgxLCAzNDgyLCAzNDgzLCAzNDg0LCAzNDg1LCAzNDg2LCAzNDg3LCAzNDg4LCAzNDg5LCAzNDkwLCAzNDkxLCAzNDkyLCAzNDkzLCAzNDk0LCAzNDk1LCAzNDk2LCAzNDk3LCAzNDk4LCAzNDk5LCAzNTAwLCAzNTAxLCAzNTAyLCAzNTAzLCAzNTA0LCAzNTA1LCAzNTA2LCAzNTA3LCAzNTA4LCAzNTA5LCAzNTEwLCAzNTExLCAzNTEyLCAzNTEzLCAzNTE0LCAzNTE1LCAzNTE2LCAzNTE3LCAzNTE4LCAzNTE5LCAzNTIwLCAzNTIxLCAzNTIyLCAzNTIzLCAzNTI0LCAzNTI1LCAzNTI2LCAzNTI3LCAzNTI4LCAzNTI5LCAzNTMwLCAzNTMxLCAzNTMyLCAzNTMzLCAzNTM0LCAzNTM1LCAzNTM2LCAzNTM3LCAzNTM4LCAzNTM5LCAzNTQwLCAzNTQxLCAzNTQyLCAzNTQzLCAzNTQ0LCAzNTQ1LCAzNTQ2LCAzNTQ3LCAzNTQ4LCAzNTQ5LCAzNTUwLCAzNTUxLCAzNTUyLCAzNTUzLCAzNTU0LCAzNTU1LCAzNTU2LCAzNTU3LCAzNTU4LCAzNTU5LCAzNTYwLCAzNTYxLCAzNTYyLCAzNTYzLCAzNTY0LCAzNTY1LCAzNTY2LCAzNTY3LCAzNTY4LCAzNTY5LCAzNTcwLCAzNTcxLCAzNTcyLCAzNTczLCAzNTc0LCAzNTc1LCAzNTc2LCAzNTc3LCAzNTc4LCAzNTc5LCAzNTgwLCAzNTgxLCAzNTgyLCAzNTgzLCAzNTg0LCAzNTg1LCAzNTg2LCAzNTg3LCAzNTg4LCAzNTg5LCAzNTkwLCAzNTkxLCAzNTkyLCAzNTkzLCAzNTk0LCAzNTk1LCAzNTk2LCAzNTk3LCAzNTk4LCAzNTk5LCAzNjAwLCAzNjAxLCAzNjAyLCAzNjAzLCAzNjA0LCAzNjA1LCAzNjA2LCAzNjA3LCAzNjA4LCAzNjA5LCAzNjEwLCAzNjExLCAzNjEyLCAzNjEzLCAzNjE0LCAzNjE1LCAzNjE2LCAzNjE3LCAzNjE4LCAzNjE5LCAzNjIwLCAzNjIxLCAzNjIyLCAzNjIzLCAzNjI0LCAzNjI1LCAzNjI2LCAzNjI3LCAzNjI4LCAzNjI5LCAzNjMwLCAzNjMxLCAzNjMyLCAzNjMzLCAzNjM0LCAzNjM1LCAzNjM2LCAzNjM3LCAzNjM4LCAzNjM5LCAzNjQwLCAzNjQxLCAzNjQyLCAzNjQzLCAzNjQ0LCAzNjQ1LCAzNjQ2LCAzNjQ3LCAzNjQ4LCAzNjQ5LCAzNjUwLCAzNjUxLCAzNjUyLCAzNjUzLCAzNjU0LCAzNjU1LCAzNjU2LCAzNjU3LCAzNjU4LCAzNjU5LCAzNjYwLCAzNjYxLCAzNjYyLCAzNjYzLCAzNjY0LCAzNjY1LCAzNjY2LCAzNjY3LCAzNjY4LCAzNjY5LCAzNjcwLCAzNjcxLCAzNjcyLCAzNjczLCAzNjc0LCAzNjc1LCAzNjc2LCAzNjc3LCAzNjc4LCAzNjc5LCAzNjgwLCAzNjgxLCAzNjgyLCAzNjgzLCAzNjg0LCAzNjg1LCAzNjg2LCAzNjg3LCAzNjg4LCAzNjg5LCAzNjkwLCAzNjkxLCAzNjkyLCAzNjkzLCAzNjk0LCAzNjk1LCAzNjk2LCAzNjk3LCAzNjk4LCAzNjk5LCAzNzAwLCAzNzAxLCAzNzAyLCAzNzAzLCAzNzA0LCAzNzA1LCAzNzA2LCAzNzA3LCAzNzA4LCAzNzA5LCAzNzEwLCAzNzExLCAzNzEyLCAzNzEzLCAzNzE0LCAzNzE1LCAzNzE2LCAzNzE3LCAzNzE4LCAzNzE5LCAzNzIwLCAzNzIxLCAzNzIyLCAzNzIzLCAzNzI0LCAzNzI1LCAzNzI2LCAzNzI3LCAzNzI4LCAzNzI5LCAzNzMwLCAzNzMxLCAzNzMyLCAzNzMzLCAzNzM0LCAzNzM1LCAzNzM2LCAzNzM3LCAzNzM4XZQu"
                             },
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 21,
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVaQIAAAAAAAB9lIwEdGV4dJRYWAIAAFszMDM1LCAzMDM2LCAzMDM3LCAzMDM4LCAzMDM5LCAzMDQwLCAzMDQxLCAzMDQyLCAzMDQzLCAzMDQ0LCAzMDQ1LCAzMDQ2LCAzMDQ3LCAzMDQ4LCAzMDQ5LCAzMDUwLCAzMDUxLCAzMDUyLCAzMDUzLCAzMDU0LCAzMDU1LCAzMDU2LCAzMDU3LCAzMDU4LCAzMDU5LCAzMDYwLCAzMDYxLCAzMDYyLCAzMDYzLCAzMDY0LCAzMDY1LCAzMDY2LCAzMDY3LCAzMDY4LCAzMDY5LCAzMDcwLCAzMDcxLCAzMDcyLCAzMDczLCAzMDc0LCAzMDc1LCAzMDc2LCAzMDc3LCAzMDc4LCAzMDc5LCAzMDgwLCAzMDgxLCAzMDgyLCAzMDgzLCAzMDg0LCAzMDg1LCAzMDg2LCAzMDg3LCAzMDg4LCAzMDg5LCAzMDkwLCAzMDkxLCAzMDkyLCAzMDkzLCAzMDk0LCAzMDk1LCAzMDk2LCAzMDk3LCAzMDk4LCAzMDk5LCAzMTAwLCAzMTAxLCAzMTAyLCAzMTAzLCAzMTA0LCAzMTA1LCAzMTA2LCAzMTA3LCAzMTA4LCAzMTA5LCAzMTEwLCAzMTExLCAzMTEyLCAzMTEzLCAzMTE0LCAzMTE1LCAzMTE2LCAzMTE3LCAzMTE4LCAzMTE5LCAzMTIwLCAzMTIxLCAzMTIyLCAzMTIzLCAzMTI0LCAzMTI1LCAzMTI2LCAzMTI3LCAzMTI4LCAzMTI5LCAzMTMwLCAzMTMxLCAzMTMyLCAzMTMzLCAzMTM0XZRzLg=="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 22
-                            }
-                        ],
-                        "GID": 19,
-                        "pos x": 724.2936205842011,
-                        "pos y": 249.9898748171978,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.GetVar_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "get var"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 24,
-                                "val": "gASVBQAAAAAAAACMAWGULg==",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMAWGUcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 25
-                            }
-                        ],
-                        "GID": 23,
-                        "pos x": 164.80323966981427,
-                        "pos y": 368.23084671814206,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Clock_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {
-                                "start": {
-                                    "method": "start"
-                                },
-                                "stop": {
-                                    "method": "stop"
-                                }
+                            "has widget": true,
+                            "widget name": "val",
+                            "widget pos": "besides",
+                            "widget data": "gASV9BAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTR4SjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUWIYQAABbMzAzNSwgMzAzNiwgMzAzNywgMzAzOCwgMzAzOSwgMzA0MCwgMzA0MSwgMzA0MiwgMzA0MywgMzA0NCwgMzA0NSwgMzA0NiwgMzA0NywgMzA0OCwgMzA0OSwgMzA1MCwgMzA1MSwgMzA1MiwgMzA1MywgMzA1NCwgMzA1NSwgMzA1NiwgMzA1NywgMzA1OCwgMzA1OSwgMzA2MCwgMzA2MSwgMzA2MiwgMzA2MywgMzA2NCwgMzA2NSwgMzA2NiwgMzA2NywgMzA2OCwgMzA2OSwgMzA3MCwgMzA3MSwgMzA3MiwgMzA3MywgMzA3NCwgMzA3NSwgMzA3NiwgMzA3NywgMzA3OCwgMzA3OSwgMzA4MCwgMzA4MSwgMzA4MiwgMzA4MywgMzA4NCwgMzA4NSwgMzA4NiwgMzA4NywgMzA4OCwgMzA4OSwgMzA5MCwgMzA5MSwgMzA5MiwgMzA5MywgMzA5NCwgMzA5NSwgMzA5NiwgMzA5NywgMzA5OCwgMzA5OSwgMzEwMCwgMzEwMSwgMzEwMiwgMzEwMywgMzEwNCwgMzEwNSwgMzEwNiwgMzEwNywgMzEwOCwgMzEwOSwgMzExMCwgMzExMSwgMzExMiwgMzExMywgMzExNCwgMzExNSwgMzExNiwgMzExNywgMzExOCwgMzExOSwgMzEyMCwgMzEyMSwgMzEyMiwgMzEyMywgMzEyNCwgMzEyNSwgMzEyNiwgMzEyNywgMzEyOCwgMzEyOSwgMzEzMCwgMzEzMSwgMzEzMiwgMzEzMywgMzEzNCwgMzEzNCwgMzEzNSwgMzEzNiwgMzEzNywgMzEzOCwgMzEzOSwgMzE0MCwgMzE0MSwgMzE0MiwgMzE0MywgMzE0NCwgMzE0NSwgMzE0NiwgMzE0NywgMzE0OCwgMzE0OSwgMzE1MCwgMzE1MSwgMzE1MiwgMzE1MywgMzE1NCwgMzE1NSwgMzE1NiwgMzE1NywgMzE1OCwgMzE1OSwgMzE2MCwgMzE2MSwgMzE2MiwgMzE2MywgMzE2NCwgMzE2NSwgMzE2NiwgMzE2NywgMzE2OCwgMzE2OSwgMzE3MCwgMzE3MSwgMzE3MiwgMzE3MywgMzE3NCwgMzE3NSwgMzE3NiwgMzE3NywgMzE3OCwgMzE3OSwgMzE4MCwgMzE4MSwgMzE4MiwgMzE4MywgMzE4NCwgMzE4NSwgMzE4NiwgMzE4NywgMzE4OCwgMzE4OSwgMzE5MCwgMzE5MSwgMzE5MiwgMzE5MywgMzE5NCwgMzE5NSwgMzE5NiwgMzE5NywgMzE5OCwgMzE5OSwgMzIwMCwgMzIwMSwgMzIwMiwgMzIwMywgMzIwNCwgMzIwNSwgMzIwNiwgMzIwNywgMzIwOCwgMzIwOSwgMzIxMCwgMzIxMSwgMzIxMiwgMzIxMywgMzIxNCwgMzIxNSwgMzIxNiwgMzIxNywgMzIxOCwgMzIxOSwgMzIyMCwgMzIyMSwgMzIyMiwgMzIyMywgMzIyNCwgMzIyNSwgMzIyNiwgMzIyNywgMzIyOCwgMzIyOSwgMzIzMCwgMzIzMSwgMzIzMiwgMzIzMywgMzIzNCwgMzIzNSwgMzIzNiwgMzIzNywgMzIzOCwgMzIzOSwgMzI0MCwgMzI0MSwgMzI0MiwgMzI0MywgMzI0NCwgMzI0NSwgMzI0NiwgMzI0NywgMzI0OCwgMzI0OSwgMzI1MCwgMzI1MSwgMzI1MiwgMzI1MywgMzI1NCwgMzI1NSwgMzI1NiwgMzI1NywgMzI1OCwgMzI1OSwgMzI2MCwgMzI2MSwgMzI2MiwgMzI2MywgMzI2NCwgMzI2NSwgMzI2NiwgMzI2NywgMzI2OCwgMzI2OSwgMzI3MCwgMzI3MSwgMzI3MiwgMzI3MywgMzI3NCwgMzI3NSwgMzI3NiwgMzI3NywgMzI3OCwgMzI3OSwgMzI4MCwgMzI4MSwgMzI4MiwgMzI4MywgMzI4NCwgMzI4NSwgMzI4NiwgMzI4NywgMzI4OCwgMzI4OSwgMzI5MCwgMzI5MSwgMzI5MiwgMzI5MywgMzI5NCwgMzI5NSwgMzI5NiwgMzI5NywgMzI5OCwgMzI5OSwgMzMwMCwgMzMwMSwgMzMwMiwgMzMwMywgMzMwNCwgMzMwNSwgMzMwNiwgMzMwNywgMzMwOCwgMzMwOSwgMzMxMCwgMzMxMSwgMzMxMiwgMzMxMywgMzMxNCwgMzMxNSwgMzMxNiwgMzMxNywgMzMxOCwgMzMxOSwgMzMyMCwgMzMyMSwgMzMyMiwgMzMyMywgMzMyNCwgMzMyNSwgMzMyNiwgMzMyNywgMzMyOCwgMzMyOSwgMzMzMCwgMzMzMSwgMzMzMiwgMzMzMywgMzMzNCwgMzMzNSwgMzMzNiwgMzMzNywgMzMzOCwgMzMzOSwgMzM0MCwgMzM0MSwgMzM0MiwgMzM0MywgMzM0NCwgMzM0NSwgMzM0NiwgMzM0NywgMzM0OCwgMzM0OSwgMzM1MCwgMzM1MSwgMzM1MiwgMzM1MywgMzM1NCwgMzM1NSwgMzM1NiwgMzM1NywgMzM1OCwgMzM1OSwgMzM2MCwgMzM2MSwgMzM2MiwgMzM2MywgMzM2NCwgMzM2NSwgMzM2NiwgMzM2NywgMzM2OCwgMzM2OSwgMzM3MCwgMzM3MSwgMzM3MiwgMzM3MywgMzM3NCwgMzM3NSwgMzM3NiwgMzM3NywgMzM3OCwgMzM3OSwgMzM4MCwgMzM4MSwgMzM4MiwgMzM4MywgMzM4NCwgMzM4NSwgMzM4NiwgMzM4NywgMzM4OCwgMzM4OSwgMzM5MCwgMzM5MSwgMzM5MiwgMzM5MywgMzM5NCwgMzM5NSwgMzM5NiwgMzM5NywgMzM5OCwgMzM5OSwgMzQwMCwgMzQwMSwgMzQwMiwgMzQwMywgMzQwNCwgMzQwNSwgMzQwNiwgMzQwNywgMzQwOCwgMzQwOSwgMzQxMCwgMzQxMSwgMzQxMiwgMzQxMywgMzQxNCwgMzQxNSwgMzQxNiwgMzQxNywgMzQxOCwgMzQxOSwgMzQyMCwgMzQyMSwgMzQyMiwgMzQyMywgMzQyNCwgMzQyNSwgMzQyNiwgMzQyNywgMzQyOCwgMzQyOSwgMzQzMCwgMzQzMSwgMzQzMiwgMzQzMywgMzQzNCwgMzQzNSwgMzQzNiwgMzQzNywgMzQzOCwgMzQzOSwgMzQ0MCwgMzQ0MSwgMzQ0MiwgMzQ0MywgMzQ0NCwgMzQ0NSwgMzQ0NiwgMzQ0NywgMzQ0OCwgMzQ0OSwgMzQ1MCwgMzQ1MSwgMzQ1MiwgMzQ1MywgMzQ1NCwgMzQ1NSwgMzQ1NiwgMzQ1NywgMzQ1OCwgMzQ1OSwgMzQ2MCwgMzQ2MSwgMzQ2MiwgMzQ2MywgMzQ2NCwgMzQ2NSwgMzQ2NiwgMzQ2NywgMzQ2OCwgMzQ2OSwgMzQ3MCwgMzQ3MSwgMzQ3MiwgMzQ3MywgMzQ3NCwgMzQ3NSwgMzQ3NiwgMzQ3NywgMzQ3OCwgMzQ3OSwgMzQ4MCwgMzQ4MSwgMzQ4MiwgMzQ4MywgMzQ4NCwgMzQ4NSwgMzQ4NiwgMzQ4NywgMzQ4OCwgMzQ4OSwgMzQ5MCwgMzQ5MSwgMzQ5MiwgMzQ5MywgMzQ5NCwgMzQ5NSwgMzQ5NiwgMzQ5NywgMzQ5OCwgMzQ5OSwgMzUwMCwgMzUwMSwgMzUwMiwgMzUwMywgMzUwNCwgMzUwNSwgMzUwNiwgMzUwNywgMzUwOCwgMzUwOSwgMzUxMCwgMzUxMSwgMzUxMiwgMzUxMywgMzUxNCwgMzUxNSwgMzUxNiwgMzUxNywgMzUxOCwgMzUxOSwgMzUyMCwgMzUyMSwgMzUyMiwgMzUyMywgMzUyNCwgMzUyNSwgMzUyNiwgMzUyNywgMzUyOCwgMzUyOSwgMzUzMCwgMzUzMSwgMzUzMiwgMzUzMywgMzUzNCwgMzUzNSwgMzUzNiwgMzUzNywgMzUzOCwgMzUzOSwgMzU0MCwgMzU0MSwgMzU0MiwgMzU0MywgMzU0NCwgMzU0NSwgMzU0NiwgMzU0NywgMzU0OCwgMzU0OSwgMzU1MCwgMzU1MSwgMzU1MiwgMzU1MywgMzU1NCwgMzU1NSwgMzU1NiwgMzU1NywgMzU1OCwgMzU1OSwgMzU2MCwgMzU2MSwgMzU2MiwgMzU2MywgMzU2NCwgMzU2NSwgMzU2NiwgMzU2NywgMzU2OCwgMzU2OSwgMzU3MCwgMzU3MSwgMzU3MiwgMzU3MywgMzU3NCwgMzU3NSwgMzU3NiwgMzU3NywgMzU3OCwgMzU3OSwgMzU4MCwgMzU4MSwgMzU4MiwgMzU4MywgMzU4NCwgMzU4NSwgMzU4NiwgMzU4NywgMzU4OCwgMzU4OSwgMzU5MCwgMzU5MSwgMzU5MiwgMzU5MywgMzU5NCwgMzU5NSwgMzU5NiwgMzU5NywgMzU5OCwgMzU5OSwgMzYwMCwgMzYwMSwgMzYwMiwgMzYwMywgMzYwNCwgMzYwNSwgMzYwNiwgMzYwNywgMzYwOCwgMzYwOSwgMzYxMCwgMzYxMSwgMzYxMiwgMzYxMywgMzYxNCwgMzYxNSwgMzYxNiwgMzYxNywgMzYxOCwgMzYxOSwgMzYyMCwgMzYyMSwgMzYyMiwgMzYyMywgMzYyNCwgMzYyNSwgMzYyNiwgMzYyNywgMzYyOCwgMzYyOSwgMzYzMCwgMzYzMSwgMzYzMiwgMzYzMywgMzYzNCwgMzYzNSwgMzYzNiwgMzYzNywgMzYzOCwgMzYzOSwgMzY0MCwgMzY0MSwgMzY0MiwgMzY0MywgMzY0NCwgMzY0NSwgMzY0NiwgMzY0NywgMzY0OCwgMzY0OSwgMzY1MCwgMzY1MSwgMzY1MiwgMzY1MywgMzY1NCwgMzY1NSwgMzY1NiwgMzY1NywgMzY1OCwgMzY1OSwgMzY2MCwgMzY2MSwgMzY2MiwgMzY2MywgMzY2NCwgMzY2NSwgMzY2NiwgMzY2NywgMzY2OCwgMzY2OSwgMzY3MCwgMzY3MSwgMzY3MiwgMzY3MywgMzY3NCwgMzY3NSwgMzY3NiwgMzY3NywgMzY3OCwgMzY3OSwgMzY4MCwgMzY4MSwgMzY4MiwgMzY4MywgMzY4NCwgMzY4NSwgMzY4NiwgMzY4NywgMzY4OCwgMzY4OSwgMzY5MCwgMzY5MSwgMzY5MiwgMzY5MywgMzY5NCwgMzY5NSwgMzY5NiwgMzY5NywgMzY5OCwgMzY5OSwgMzcwMCwgMzcwMSwgMzcwMiwgMzcwMywgMzcwNCwgMzcwNSwgMzcwNiwgMzcwNywgMzcwOCwgMzcwOSwgMzcxMCwgMzcxMSwgMzcxMiwgMzcxMywgMzcxNCwgMzcxNSwgMzcxNiwgMzcxNywgMzcxOCwgMzcxOSwgMzcyMCwgMzcyMSwgMzcyMiwgMzcyMywgMzcyNCwgMzcyNSwgMzcyNiwgMzcyNywgMzcyOCwgMzcyOSwgMzczMCwgMzczMSwgMzczMiwgMzczMywgMzczNCwgMzczNSwgMzczNiwgMzczNywgMzczOF2UdWJzLg=="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 40,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 724.2936205842011,
+                    "pos y": 249.9898748171978,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "set var"
+                },
+                {
+                    "GID": 41,
+                    "version": "v0.2",
+                    "identifier": "built_in.GetVar_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 44,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 120,
+                                "identifier": "Data",
+                                "serialized": "gASVBQAAAAAAAACMAWGULg=="
                             },
-                            "display title": "clock"
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTR8SjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 45,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {
+                            "a": "var_val_changed"
+                        }
+                    },
+                    "pos x": 164.80323966981427,
+                    "pos y": 368.23084671814206,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
                         },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "delay",
-                                "GID": 27,
-                                "val": "gASVCgAAAAAAAABHP4R64UeuFHsu",
-                                "dtype": "DType.Float",
-                                "dtype state": "gASVRwAAAAAAAAB9lCiMB2RlZmF1bHSURz+5mZmZmZmajAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowIZGVjaW1hbHOUSwp1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVEgAAAAAAAAB9lIwEdGV4dJSMBDAuMDGUcy4="
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "get var"
+                },
+                {
+                    "GID": 46,
+                    "version": "v0.2",
+                    "identifier": "std.Clock_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 50,
+                            "type": "data",
+                            "label": "delay",
+                            "default": {
+                                "GID": 153,
+                                "identifier": "Data",
+                                "serialized": "gARLAC4="
                             },
-                            {
-                                "type": "data",
-                                "label": "iterations",
-                                "GID": 28,
-                                "val": "gARLZC4=",
-                                "dtype": "DType.Integer",
-                                "dtype state": "gASVOAAAAAAAAAB9lCiMB2RlZmF1bHSUSv////+MA3ZhbJRLAIwDZG9jlIwAlIwGYm91bmRzlEr/////TegDhpR1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVDAAAAAAAAAB9lIwDdmFslEtkcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "exec",
-                                "label": "",
-                                "GID": 29
-                            }
-                        ],
-                        "GID": 26,
-                        "pos x": 231.23622828581165,
-                        "pos y": 114.6139516414438,
-                        "main widget data": "gAR9lC4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.SetVar_Node",
-                        "version": null,
-                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIhzLg==",
-                        "additional data": {
-                            "special actions": {
-                                "make passive": {
-                                    "method": "action_make_passive"
-                                }
+                            "has widget": true,
+                            "widget name": "delay",
+                            "widget pos": "besides",
+                            "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSASjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwB1YnMu"
+                        },
+                        {
+                            "GID": 51,
+                            "type": "data",
+                            "label": "iterations",
+                            "default": {
+                                "GID": 54,
+                                "identifier": "Data",
+                                "serialized": "gASVBgAAAAAAAABK/////y4="
                             },
-                            "display title": "set var"
+                            "has widget": true,
+                            "widget name": "iter",
+                            "widget pos": "besides",
+                            "widget data": "gASVbQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSESjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSv////91YnMu"
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 52,
+                            "type": "exec",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 230.23622828581165,
+                    "pos y": 115.6139516414438,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "start": {
+                            "method": "start"
                         },
-                        "inputs": [
-                            {
-                                "type": "exec",
-                                "label": "",
-                                "GID": 31
+                        "stop": {
+                            "method": "stop"
+                        }
+                    },
+                    "display title": "clock"
+                },
+                {
+                    "GID": 55,
+                    "version": "v0.1",
+                    "identifier": "built_in.SetVar_Node",
+                    "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIhzLg==",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 61,
+                            "type": "exec",
+                            "label": ""
+                        },
+                        {
+                            "GID": 62,
+                            "type": "data",
+                            "label": "var",
+                            "default": {
+                                "GID": 109,
+                                "identifier": "Data",
+                                "serialized": "gASVBwAAAAAAAACMA2N0cpQu"
                             },
-                            {
-                                "type": "data",
-                                "label": "var",
-                                "GID": 32,
-                                "val": "gASVBwAAAAAAAACMA2N0cpQu",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA2N0cpRzLg=="
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSISjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANjdHKUdWJzLg=="
+                        },
+                        {
+                            "GID": 63,
+                            "type": "data",
+                            "label": "val",
+                            "default": {
+                                "GID": 4634,
+                                "identifier": "Data",
+                                "serialized": "gASVCAAAAAAAAACMBDM3MzmULg=="
                             },
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 33,
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVEgAAAAAAAAB9lIwEdGV4dJSMBDMxMzWUcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "exec",
-                                "label": "",
-                                "GID": 34
+                            "has widget": true,
+                            "widget name": "val",
+                            "widget pos": "besides",
+                            "widget data": "gASVbwAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSMSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAQzNzM5lHVicy4="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 64,
+                            "type": "exec",
+                            "label": ""
+                        },
+                        {
+                            "GID": 65,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 846.1910218628586,
+                    "pos y": 133.30644603168525,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "set var"
+                },
+                {
+                    "GID": 66,
+                    "version": "v0.2",
+                    "identifier": "built_in.GetVar_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 69,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 113,
+                                "identifier": "Data",
+                                "serialized": "gASVBwAAAAAAAACMA2N0cpQu"
                             },
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 35
-                            }
-                        ],
-                        "GID": 30,
-                        "pos x": 846.1910218628586,
-                        "pos y": 133.30644603168525,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.GetVar_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "get var"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 37,
-                                "val": "gASVBwAAAAAAAACMA2N0cpQu",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA2N0cpRzLg=="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 38
-                            }
-                        ],
-                        "GID": 36,
-                        "pos x": 196.31117068512344,
-                        "pos y": 265.7809649154535,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Plus_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {
-                                "add input": {
-                                    "method": "add_operand_input"
-                                },
-                                "remove input 0": {
-                                    "method": "remove_operand_input",
-                                    "data": 0
-                                },
-                                "remove input 1": {
-                                    "method": "remove_operand_input",
-                                    "data": 1
-                                }
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSQSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANjdHKUdWJzLg=="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 70,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {
+                            "ctr": "var_val_changed"
+                        }
+                    },
+                    "pos x": 196.31117068512344,
+                    "pos y": 265.7809649154535,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "get var"
+                },
+                {
+                    "GID": 71,
+                    "version": "v0.2",
+                    "identifier": "std.Plus_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 75,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 4632,
+                                "identifier": "Data",
+                                "serialized": "gASVBAAAAAAAAABNmg4u"
                             },
-                            "display title": "+"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 40,
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVEgAAAAAAAAB9lIwEdGV4dJSMBDMxMzSUcy4="
+                            "has widget": true,
+                            "widget name": "in",
+                            "widget pos": "besides",
+                            "widget data": "gASVawAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSUSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUTZoOdWJzLg=="
+                        },
+                        {
+                            "GID": 76,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 110,
+                                "identifier": "Data",
+                                "serialized": "gARLAS4="
                             },
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 41,
-                                "val": "gARLAS4=",
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMATGUcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 42
-                            }
-                        ],
-                        "GID": 39,
-                        "pos x": 585.6887913914879,
-                        "pos y": 174.51150293252385,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.LinkIN_Node",
-                        "version": null,
-                        "state data": "gASVMAAAAAAAAAB9lIwCSUSUjCRjNWVmZjE5MC0yNzllLTQ3M2MtYTQ3Ny1iNjJmMjg0MGUzOTiUcy4=",
-                        "additional data": {
-                            "special actions": {
-                                "add input": {
-                                    "method": "add_inp"
-                                },
-                                "remove inp": {},
-                                "copy ID": {
-                                    "method": "copy_ID"
-                                }
+                            "has widget": true,
+                            "widget name": "in",
+                            "widget pos": "besides",
+                            "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTSYSjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 77,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 478.68879139148794,
+                    "pos y": 168.51150293252385,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "add input": {
+                            "method": "add_operand_input"
+                        },
+                        "remove input": {
+                            "0": {
+                                "method": "remove_operand_input",
+                                "data": 0
                             },
-                            "display title": "link"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 44,
-                                "has widget": false
+                            "1": {
+                                "method": "remove_operand_input",
+                                "data": 1
                             }
-                        ],
-                        "outputs": [],
-                        "GID": 43,
-                        "pos x": 566.7044488232787,
-                        "pos y": 478.94789784811906,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    }
-                ],
-                "connections": [
-                    {
-                        "GID": 45,
-                        "parent node index": 0,
-                        "output port index": 0,
-                        "connected node": 5,
-                        "connected input port index": 1
-                    },
-                    {
-                        "GID": 46,
-                        "parent node index": 1,
-                        "output port index": 0,
-                        "connected node": 2,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 47,
-                        "parent node index": 3,
-                        "output port index": 0,
-                        "connected node": 4,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 48,
-                        "parent node index": 3,
-                        "output port index": 0,
-                        "connected node": 11,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 49,
-                        "parent node index": 6,
-                        "output port index": 0,
-                        "connected node": 1,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 50,
-                        "parent node index": 7,
-                        "output port index": 0,
-                        "connected node": 8,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 51,
-                        "parent node index": 9,
-                        "output port index": 0,
-                        "connected node": 0,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 52,
-                        "parent node index": 9,
-                        "output port index": 0,
-                        "connected node": 10,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 53,
-                        "parent node index": 10,
-                        "output port index": 0,
-                        "connected node": 8,
-                        "connected input port index": 2
-                    }
-                ],
-                "GID": 4,
-                "flow view": {
-                    "drawings": [],
-                    "view size": [
-                        6400.0,
-                        4800.0
+                        }
+                    },
+                    "display title": "+"
+                },
+                {
+                    "GID": 80,
+                    "version": "v0.2",
+                    "identifier": "std.LinkIN_Node",
+                    "state data": "gASVMAAAAAAAAAB9lIwCSUSUjCRjNWVmZjE5MC0yNzllLTQ3M2MtYTQ3Ny1iNjJmMjg0MGUzOTiUcy4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 82,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 566.7044488232787,
+                    "pos y": 478.94789784811906,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "add input": {
+                            "method": "add_inp"
+                        },
+                        "remove inp": {},
+                        "copy ID": {
+                            "method": "copy_ID"
+                        }
+                    },
+                    "display title": "link IN"
+                },
+                {
+                    "GID": 103,
+                    "version": "v0.2",
+                    "identifier": "std.Button_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [],
+                    "outputs": [
+                        {
+                            "GID": 104,
+                            "type": "exec",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 473.0,
+                    "pos y": 51.0,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "Button"
+                }
+            ],
+            "connections": [
+                {
+                    "parent node index": 0,
+                    "output port index": 0,
+                    "connected node": 5,
+                    "connected input port index": 1
+                },
+                {
+                    "parent node index": 1,
+                    "output port index": 0,
+                    "connected node": 2,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 3,
+                    "output port index": 0,
+                    "connected node": 4,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 3,
+                    "output port index": 0,
+                    "connected node": 11,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 6,
+                    "output port index": 0,
+                    "connected node": 1,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 7,
+                    "output port index": 0,
+                    "connected node": 8,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 9,
+                    "output port index": 0,
+                    "connected node": 0,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 9,
+                    "output port index": 0,
+                    "connected node": 10,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 10,
+                    "output port index": 0,
+                    "connected node": 8,
+                    "connected input port index": 2
+                },
+                {
+                    "parent node index": 12,
+                    "output port index": 0,
+                    "connected node": 8,
+                    "connected input port index": 0
+                }
+            ],
+            "output data": [
+                {
+                    "data": {
+                        "GID": 4627,
+                        "identifier": "Data",
+                        "serialized": "gASVSAgAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lLg=="
+                    },
+                    "dependent node outputs": [
+                        0,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 4631,
+                        "identifier": "Data",
+                        "serialized": "gASVBAAAAAAAAABNmg4u"
+                    },
+                    "dependent node outputs": [
+                        1,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 151,
+                        "identifier": "Data",
+                        "serialized": "gASVCgAAAAAAAABHP+haHKwIMScu"
+                    },
+                    "dependent node outputs": [
+                        3,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 4630,
+                        "identifier": "Data",
+                        "serialized": "gASVSAgAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lLg=="
+                    },
+                    "dependent node outputs": [
+                        6,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 4626,
+                        "identifier": "Data",
+                        "serialized": "gASVBAAAAAAAAABNmg4u"
+                    },
+                    "dependent node outputs": [
+                        9,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 4633,
+                        "identifier": "Data",
+                        "serialized": "gASVBAAAAAAAAABNmw4u"
+                    },
+                    "dependent node outputs": [
+                        10,
+                        0
                     ]
                 }
-            },
-            "GID": 1
+            ],
+            "flow view": {
+                "drawings": [],
+                "view size": [
+                    10000.0,
+                    7000.0
+                ]
+            }
         },
-        {
-            "title": "script 2",
-            "variables": {},
-            "flow": {
-                "algorithm mode": "data",
-                "nodes": [
-                    {
-                        "identifier": "std.LinkOUT_Node",
-                        "version": null,
-                        "state data": "gASVNwAAAAAAAAB9lIwJbGlua2VkIElElIwkYzVlZmYxOTAtMjc5ZS00NzNjLWE0NzctYjYyZjI4NDBlMzk4lHMu",
-                        "additional data": {
-                            "special actions": {
-                                "link to ID": {
-                                    "method": "choose_link_ID"
-                                }
-                            },
-                            "display title": "link"
-                        },
-                        "inputs": [],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 59
-                            }
-                        ],
-                        "GID": 58,
-                        "pos x": 384.5648465384588,
-                        "pos y": 259.39811467640766,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.Result_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "result"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 61,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [],
-                        "GID": 60,
-                        "pos x": 642.5648465384588,
-                        "pos y": 259.39811467640766,
-                        "main widget data": "gAR9lC4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    }
-                ],
-                "connections": [
-                    {
-                        "GID": 62,
-                        "parent node index": 0,
-                        "output port index": 0,
-                        "connected node": 1,
-                        "connected input port index": 0
-                    }
-                ],
-                "GID": 57,
-                "flow view": {
-                    "drawings": [],
-                    "view size": [
-                        6400.0,
-                        4800.0
+        "script 2": {
+            "GID": 83,
+            "algorithm mode": "data",
+            "nodes": [
+                {
+                    "GID": 84,
+                    "version": "v0.2",
+                    "identifier": "std.LinkOUT_Node",
+                    "state data": "gASVNwAAAAAAAAB9lIwJbGlua2VkIElElIwkYzVlZmYxOTAtMjc5ZS00NzNjLWE0NzctYjYyZjI4NDBlMzk4lHMu",
+                    "additional data": {},
+                    "inputs": [],
+                    "outputs": [
+                        {
+                            "GID": 85,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "pos x": 384.5648465384588,
+                    "pos y": 259.39811467640766,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "link to ID": {
+                            "method": "link_to_ID"
+                        }
+                    },
+                    "display title": "link OUT"
+                },
+                {
+                    "GID": 86,
+                    "version": "v0.2",
+                    "identifier": "built_in.Result_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 88,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [],
+                    "pos x": 642.5648465384588,
+                    "pos y": 259.39811467640766,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "result"
+                }
+            ],
+            "connections": [
+                {
+                    "parent node index": 0,
+                    "output port index": 0,
+                    "connected node": 1,
+                    "connected input port index": 0
+                }
+            ],
+            "output data": [
+                {
+                    "data": {
+                        "GID": 151,
+                        "identifier": "Data",
+                        "serialized": "gASVCgAAAAAAAABHP+haHKwIMScu"
+                    },
+                    "dependent node outputs": [
+                        0,
+                        0
                     ]
                 }
-            },
-            "GID": 54
+            ],
+            "flow view": {
+                "drawings": [],
+                "view size": [
+                    10000.0,
+                    7000.0
+                ]
+            }
+        }
+    },
+    "addons": {
+        "Variables": {
+            "GID": 3,
+            "version": "0.4",
+            "custom state": {
+                "5": {
+                    "a": {
+                        "GID": 4629,
+                        "identifier": "Data",
+                        "serialized": "gASVSAgAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxNPgxNPwxNQAxNQQxNQgxNQwxNRAxNRQxNRgxNRwxNSAxNSQxNSgxNSwxNTAxNTQxNTgxNTwxNUAxNUQxNUgxNUwxNVAxNVQxNVgxNVwxNWAxNWQxNWgxNWwxNXAxNXQxNXgxNXwxNYAxNYQxNYgxNYwxNZAxNZQxNZgxNZwxNaAxNaQxNagxNawxNbAxNbQxNbgxNbwxNcAxNcQxNcgxNcwxNdAxNdQxNdgxNdwxNeAxNeQxNegxNewxNfAxNfQxNfgxNfwxNgAxNgQxNggxNgwxNhAxNhQxNhgxNhwxNiAxNiQxNigxNiwxNjAxNjQxNjgxNjwxNkAxNkQxNkgxNkwxNlAxNlQxNlgxNlwxNmAxNmQxNmgxNmwxNnAxNnQxNngxNnwxNoAxNoQxNogxNowxNpAxNpQxNpgxNpwxNqAxNqQxNqgxNqwxNrAxNrQxNrgxNrwxNsAxNsQxNsgxNswxNtAxNtQxNtgxNtwxNuAxNuQxNugxNuwxNvAxNvQxNvgxNvwxNwAxNwQxNwgxNwwxNxAxNxQxNxgxNxwxNyAxNyQxNygxNywxNzAxNzQxNzgxNzwxN0AxN0QxN0gxN0wxN1AxN1QxN1gxN1wxN2AxN2QxN2gxN2wxN3AxN3QxN3gxN3wxN4AxN4QxN4gxN4wxN5AxN5QxN5gxN5wxN6AxN6QxN6gxN6wxN7AxN7QxN7gxN7wxN8AxN8QxN8gxN8wxN9AxN9QxN9gxN9wxN+AxN+QxN+gxN+wxN/AxN/QxN/gxN/wxNAA1NAQ1NAg1NAw1NBA1NBQ1NBg1NBw1NCA1NCQ1NCg1NCw1NDA1NDQ1NDg1NDw1NEA1NEQ1NEg1NEw1NFA1NFQ1NFg1NFw1NGA1NGQ1NGg1NGw1NHA1NHQ1NHg1NHw1NIA1NIQ1NIg1NIw1NJA1NJQ1NJg1NJw1NKA1NKQ1NKg1NKw1NLA1NLQ1NLg1NLw1NMA1NMQ1NMg1NMw1NNA1NNQ1NNg1NNw1NOA1NOQ1NOg1NOw1NPA1NPQ1NPg1NPw1NQA1NQQ1NQg1NQw1NRA1NRQ1NRg1NRw1NSA1NSQ1NSg1NSw1NTA1NTQ1NTg1NTw1NUA1NUQ1NUg1NUw1NVA1NVQ1NVg1NVw1NWA1NWQ1NWg1NWw1NXA1NXQ1NXg1NXw1NYA1NYQ1NYg1NYw1NZA1NZQ1NZg1NZw1NaA1NaQ1Nag1Naw1NbA1NbQ1Nbg1Nbw1NcA1NcQ1Ncg1Ncw1NdA1NdQ1Ndg1Ndw1NeA1NeQ1Neg1New1NfA1NfQ1Nfg1Nfw1NgA1NgQ1Ngg1Ngw1NhA1NhQ1Nhg1Nhw1NiA1NiQ1Nig1Niw1NjA1NjQ1Njg1Njw1NkA1NkQ1Nkg1Nkw1NlA1NlQ1Nlg1Nlw1NmA1NmQ1Nmg1Nmw1NnA1NnQ1Nng1Nnw1NoA1NoQ1Nog1Now1NpA1NpQ1Npg1Npw1NqA1NqQ1Nqg1Nqw1NrA1NrQ1Nrg1Nrw1NsA1NsQ1Nsg1Nsw1NtA1NtQ1Ntg1Ntw1NuA1NuQ1Nug1Nuw1NvA1NvQ1Nvg1Nvw1NwA1NwQ1Nwg1Nww1NxA1NxQ1Nxg1Nxw1NyA1NyQ1Nyg1Nyw1NzA1NzQ1Nzg1Nzw1N0A1N0Q1N0g1N0w1N1A1N1Q1N1g1N1w1N2A1N2Q1N2g1N2w1N3A1N3Q1N3g1N3w1N4A1N4Q1N4g1N4w1N5A1N5Q1N5g1N5w1N6A1N6Q1N6g1N6w1N7A1N7Q1N7g1N7w1N8A1N8Q1N8g1N8w1N9A1N9Q1N9g1N9w1N+A1N+Q1N+g1N+w1N/A1N/Q1N/g1N/w1NAA5NAQ5NAg5NAw5NBA5NBQ5NBg5NBw5NCA5NCQ5NCg5NCw5NDA5NDQ5NDg5NDw5NEA5NEQ5NEg5NEw5NFA5NFQ5NFg5NFw5NGA5NGQ5NGg5NGw5NHA5NHQ5NHg5NHw5NIA5NIQ5NIg5NIw5NJA5NJQ5NJg5NJw5NKA5NKQ5NKg5NKw5NLA5NLQ5NLg5NLw5NMA5NMQ5NMg5NMw5NNA5NNQ5NNg5NNw5NOA5NOQ5NOg5NOw5NPA5NPQ5NPg5NPw5NQA5NQQ5NQg5NQw5NRA5NRQ5NRg5NRw5NSA5NSQ5NSg5NSw5NTA5NTQ5NTg5NTw5NUA5NUQ5NUg5NUw5NVA5NVQ5NVg5NVw5NWA5NWQ5NWg5NWw5NXA5NXQ5NXg5NXw5NYA5NYQ5NYg5NYw5NZA5NZQ5NZg5NZw5NaA5NaQ5Nag5Naw5NbA5NbQ5Nbg5Nbw5NcA5NcQ5Ncg5Ncw5NdA5NdQ5Ndg5Ndw5NeA5NeQ5Neg5New5NfA5NfQ5Nfg5Nfw5NgA5NgQ5Ngg5Ngw5NhA5NhQ5Nhg5Nhw5NiA5NiQ5Nig5Niw5NjA5NjQ5Njg5Njw5NkA5NkQ5Nkg5Nkw5NlA5NlQ5Nlg5Nlw5NmA5NmQ5Nmg5lLg=="
+                    },
+                    "ctr": {
+                        "GID": 4625,
+                        "identifier": "Data",
+                        "serialized": "gASVBAAAAAAAAABNmg4u"
+                    }
+                },
+                "83": {}
+            }
+        },
+        "Logging": {
+            "GID": 4,
+            "version": "0.0.1",
+            "custom state": {}
         }
-    ]
+    }
 }
\ No newline at end of file
diff --git a/ryven/examples_projects/basics_OLD.json b/ryven/examples_projects/basics_OLD.json
new file mode 100644
index 00000000..d658ca84
--- /dev/null
+++ b/ryven/examples_projects/basics_OLD.json
@@ -0,0 +1,647 @@
+{
+    "general info": {
+        "type": "Ryven project file"
+    },
+    "required packages": [
+        {
+            "name": "std",
+            "dir": "C:\\Users\\nutri\\projects\\ryven projects\\Ryven_mmorys\\Ryven\\ryven\\example_nodes\\std"
+        }
+    ],
+    "scripts": [
+        {
+            "title": "hello world",
+            "variables": {
+                "a": {
+                    "serialized": "gASVMQEAAAAAAABdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxlLg=="
+                },
+                "ctr": {
+                    "serialized": "gASVBAAAAAAAAABNPgwu"
+                }
+            },
+            "flow": {
+                "algorithm mode": "data",
+                "nodes": [
+                    {
+                        "identifier": "std.Storage_Node",
+                        "version": null,
+                        "state data": "gASVOwEAAAAAAAB9lIwEZGF0YZRdlChN2wtN3AtN3QtN3gtN3wtN4AtN4QtN4gtN4wtN5AtN5QtN5gtN5wtN6AtN6QtN6gtN6wtN7AtN7QtN7gtN7wtN8AtN8QtN8gtN8wtN9AtN9QtN9gtN9wtN+AtN+QtN+gtN+wtN/AtN/QtN/gtN/wtNAAxNAQxNAgxNAwxNBAxNBQxNBgxNBwxNCAxNCQxNCgxNCwxNDAxNDQxNDgxNDwxNEAxNEQxNEgxNEwxNFAxNFQxNFgxNFwxNGAxNGQxNGgxNGwxNHAxNHQxNHgxNHwxNIAxNIQxNIgxNIwxNJAxNJQxNJgxNJwxNKAxNKQxNKgxNKwxNLAxNLQxNLgxNLwxNMAxNMQxNMgxNMwxNNAxNNQxNNgxNNwxNOAxNOQxNOgxNOwxNPAxNPQxNPgxlcy4=",
+                        "additional data": {
+                            "special actions": {
+                                "clear": {
+                                    "method": "clear"
+                                }
+                            },
+                            "display title": "store"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 6,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 7
+                            }
+                        ],
+                        "GID": 5,
+                        "pos x": 475.4795814751692,
+                        "pos y": 264.55248052403067,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Eval_Node",
+                        "version": null,
+                        "state data": "gASVOQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMCmlucFswXVstMV2UdS4=",
+                        "additional data": {
+                            "special actions": {
+                                "add input": {
+                                    "method": "add_param_input"
+                                },
+                                "remove input 0": {
+                                    "method": "remove_param_input",
+                                    "data": 0
+                                }
+                            },
+                            "display title": "eval"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 9,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 10
+                            }
+                        ],
+                        "GID": 8,
+                        "pos x": 462.86363383615935,
+                        "pos y": 370.17323807221123,
+                        "main widget data": "gASVGAAAAAAAAAB9lIwEdGV4dJSMCmlucFswXVstMV2Ucy4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.Result_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "result"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 12,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [],
+                        "GID": 11,
+                        "pos x": 796.5680046166863,
+                        "pos y": 369.6301042263301,
+                        "main widget data": "gAR9lC4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Slider_Node",
+                        "version": null,
+                        "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/4Q5WBBiTdXMu",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "slider"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "scl",
+                                "GID": 14,
+                                "val": "gARLAi4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASVLAAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAIwDZG9jlIwAlIwGYm91bmRzlE51Lg==",
+                                "has widget": true,
+                                "widget data": "gASVDAAAAAAAAAB9lIwDdmFslEsCcy4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "round",
+                                "GID": 15,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASVKgAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROdS4=",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwHY2hlY2tlZJSJcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 16
+                            }
+                        ],
+                        "GID": 13,
+                        "pos x": 273.19774517706185,
+                        "pos y": 503.4372312306748,
+                        "main widget data": "gASVDQAAAAAAAAB9lIwDdmFslE0VAnMu",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.Result_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "result"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 18,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [],
+                        "GID": 17,
+                        "pos x": 571.2515010942456,
+                        "pos y": 555.0662156323955,
+                        "main widget data": "gAR9lC4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.SetVar_Node",
+                        "version": null,
+                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
+                        "additional data": {
+                            "special actions": {
+                                "make active": {
+                                    "method": "action_make_active"
+                                }
+                            },
+                            "display title": "set var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "var",
+                                "GID": 20,
+                                "val": "gASVBQAAAAAAAACMAWGULg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMAWGUcy4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 21,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVaQIAAAAAAAB9lIwEdGV4dJRYWAIAAFszMDM1LCAzMDM2LCAzMDM3LCAzMDM4LCAzMDM5LCAzMDQwLCAzMDQxLCAzMDQyLCAzMDQzLCAzMDQ0LCAzMDQ1LCAzMDQ2LCAzMDQ3LCAzMDQ4LCAzMDQ5LCAzMDUwLCAzMDUxLCAzMDUyLCAzMDUzLCAzMDU0LCAzMDU1LCAzMDU2LCAzMDU3LCAzMDU4LCAzMDU5LCAzMDYwLCAzMDYxLCAzMDYyLCAzMDYzLCAzMDY0LCAzMDY1LCAzMDY2LCAzMDY3LCAzMDY4LCAzMDY5LCAzMDcwLCAzMDcxLCAzMDcyLCAzMDczLCAzMDc0LCAzMDc1LCAzMDc2LCAzMDc3LCAzMDc4LCAzMDc5LCAzMDgwLCAzMDgxLCAzMDgyLCAzMDgzLCAzMDg0LCAzMDg1LCAzMDg2LCAzMDg3LCAzMDg4LCAzMDg5LCAzMDkwLCAzMDkxLCAzMDkyLCAzMDkzLCAzMDk0LCAzMDk1LCAzMDk2LCAzMDk3LCAzMDk4LCAzMDk5LCAzMTAwLCAzMTAxLCAzMTAyLCAzMTAzLCAzMTA0LCAzMTA1LCAzMTA2LCAzMTA3LCAzMTA4LCAzMTA5LCAzMTEwLCAzMTExLCAzMTEyLCAzMTEzLCAzMTE0LCAzMTE1LCAzMTE2LCAzMTE3LCAzMTE4LCAzMTE5LCAzMTIwLCAzMTIxLCAzMTIyLCAzMTIzLCAzMTI0LCAzMTI1LCAzMTI2LCAzMTI3LCAzMTI4LCAzMTI5LCAzMTMwLCAzMTMxLCAzMTMyLCAzMTMzLCAzMTM0XZRzLg=="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 22
+                            }
+                        ],
+                        "GID": 19,
+                        "pos x": 724.2936205842011,
+                        "pos y": 249.9898748171978,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.GetVar_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "get var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 24,
+                                "val": "gASVBQAAAAAAAACMAWGULg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMAWGUcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 25
+                            }
+                        ],
+                        "GID": 23,
+                        "pos x": 164.80323966981427,
+                        "pos y": 368.23084671814206,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Clock_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {
+                                "start": {
+                                    "method": "start"
+                                },
+                                "stop": {
+                                    "method": "stop"
+                                }
+                            },
+                            "display title": "clock"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "delay",
+                                "GID": 27,
+                                "val": "gASVCgAAAAAAAABHP4R64UeuFHsu",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVRwAAAAAAAAB9lCiMB2RlZmF1bHSURz+5mZmZmZmajAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowIZGVjaW1hbHOUSwp1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVEgAAAAAAAAB9lIwEdGV4dJSMBDAuMDGUcy4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "iterations",
+                                "GID": 28,
+                                "val": "gARLZC4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASVOAAAAAAAAAB9lCiMB2RlZmF1bHSUSv////+MA3ZhbJRLAIwDZG9jlIwAlIwGYm91bmRzlEr/////TegDhpR1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVDAAAAAAAAAB9lIwDdmFslEtkcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "exec",
+                                "label": "",
+                                "GID": 29
+                            }
+                        ],
+                        "GID": 26,
+                        "pos x": 231.23622828581165,
+                        "pos y": 114.6139516414438,
+                        "main widget data": "gAR9lC4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.SetVar_Node",
+                        "version": null,
+                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIhzLg==",
+                        "additional data": {
+                            "special actions": {
+                                "make passive": {
+                                    "method": "action_make_passive"
+                                }
+                            },
+                            "display title": "set var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "exec",
+                                "label": "",
+                                "GID": 31
+                            },
+                            {
+                                "type": "data",
+                                "label": "var",
+                                "GID": 32,
+                                "val": "gASVBwAAAAAAAACMA2N0cpQu",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA2N0cpRzLg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 33,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVEgAAAAAAAAB9lIwEdGV4dJSMBDMxMzWUcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "exec",
+                                "label": "",
+                                "GID": 34
+                            },
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 35
+                            }
+                        ],
+                        "GID": 30,
+                        "pos x": 846.1910218628586,
+                        "pos y": 133.30644603168525,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.GetVar_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "get var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 37,
+                                "val": "gASVBwAAAAAAAACMA2N0cpQu",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA2N0cpRzLg=="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 38
+                            }
+                        ],
+                        "GID": 36,
+                        "pos x": 196.31117068512344,
+                        "pos y": 265.7809649154535,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Plus_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {
+                                "add input": {
+                                    "method": "add_operand_input"
+                                },
+                                "remove input 0": {
+                                    "method": "remove_operand_input",
+                                    "data": 0
+                                },
+                                "remove input 1": {
+                                    "method": "remove_operand_input",
+                                    "data": 1
+                                }
+                            },
+                            "display title": "+"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 40,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVEgAAAAAAAAB9lIwEdGV4dJSMBDMxMzSUcy4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 41,
+                                "val": "gARLAS4=",
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMATGUcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 42
+                            }
+                        ],
+                        "GID": 39,
+                        "pos x": 585.6887913914879,
+                        "pos y": 174.51150293252385,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.LinkIN_Node",
+                        "version": null,
+                        "state data": "gASVMAAAAAAAAAB9lIwCSUSUjCRjNWVmZjE5MC0yNzllLTQ3M2MtYTQ3Ny1iNjJmMjg0MGUzOTiUcy4=",
+                        "additional data": {
+                            "special actions": {
+                                "add input": {
+                                    "method": "add_inp"
+                                },
+                                "remove inp": {},
+                                "copy ID": {
+                                    "method": "copy_ID"
+                                }
+                            },
+                            "display title": "link"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 44,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [],
+                        "GID": 43,
+                        "pos x": 566.7044488232787,
+                        "pos y": 478.94789784811906,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    }
+                ],
+                "connections": [
+                    {
+                        "GID": 45,
+                        "parent node index": 0,
+                        "output port index": 0,
+                        "connected node": 5,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 46,
+                        "parent node index": 1,
+                        "output port index": 0,
+                        "connected node": 2,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 47,
+                        "parent node index": 3,
+                        "output port index": 0,
+                        "connected node": 4,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 48,
+                        "parent node index": 3,
+                        "output port index": 0,
+                        "connected node": 11,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 49,
+                        "parent node index": 6,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 50,
+                        "parent node index": 7,
+                        "output port index": 0,
+                        "connected node": 8,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 51,
+                        "parent node index": 9,
+                        "output port index": 0,
+                        "connected node": 0,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 52,
+                        "parent node index": 9,
+                        "output port index": 0,
+                        "connected node": 10,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 53,
+                        "parent node index": 10,
+                        "output port index": 0,
+                        "connected node": 8,
+                        "connected input port index": 2
+                    }
+                ],
+                "GID": 4,
+                "flow view": {
+                    "drawings": [],
+                    "view size": [
+                        6400.0,
+                        4800.0
+                    ]
+                }
+            },
+            "GID": 1
+        },
+        {
+            "title": "script 2",
+            "variables": {},
+            "flow": {
+                "algorithm mode": "data",
+                "nodes": [
+                    {
+                        "identifier": "std.LinkOUT_Node",
+                        "version": null,
+                        "state data": "gASVNwAAAAAAAAB9lIwJbGlua2VkIElElIwkYzVlZmYxOTAtMjc5ZS00NzNjLWE0NzctYjYyZjI4NDBlMzk4lHMu",
+                        "additional data": {
+                            "special actions": {
+                                "link to ID": {
+                                    "method": "choose_link_ID"
+                                }
+                            },
+                            "display title": "link"
+                        },
+                        "inputs": [],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 59
+                            }
+                        ],
+                        "GID": 58,
+                        "pos x": 384.5648465384588,
+                        "pos y": 259.39811467640766,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.Result_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "result"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 61,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [],
+                        "GID": 60,
+                        "pos x": 642.5648465384588,
+                        "pos y": 259.39811467640766,
+                        "main widget data": "gAR9lC4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    }
+                ],
+                "connections": [
+                    {
+                        "GID": 62,
+                        "parent node index": 0,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 0
+                    }
+                ],
+                "GID": 57,
+                "flow view": {
+                    "drawings": [],
+                    "view size": [
+                        6400.0,
+                        4800.0
+                    ]
+                }
+            },
+            "GID": 54
+        }
+    ]
+}
\ No newline at end of file
diff --git a/ryven/examples_projects/cute.png b/ryven/examples_projects/cute.png
deleted file mode 100644
index 2e972b8e..00000000
Binary files a/ryven/examples_projects/cute.png and /dev/null differ
diff --git a/ryven/examples_projects/matrices.json b/ryven/examples_projects/matrices.json
index eb8328ed..05f5eda8 100644
--- a/ryven/examples_projects/matrices.json
+++ b/ryven/examples_projects/matrices.json
@@ -1,602 +1,959 @@
 {
     "general info": {
-        "type": "Ryven project file"
+        "type": "Ryven project file",
+        "ryven version": "3.3.0a1"
     },
     "required packages": [
         {
-            "name": "std",
-            "dir": "C:\\Users\\nutri\\projects\\ryven projects\\Ryven_mmorys\\Ryven\\ryven\\example_nodes\\std"
+            "name": "linalg",
+            "dir": "/home/leon/projects/ryven_projects/Ryven/ryven/example_nodes/linalg"
         },
         {
-            "name": "linalg",
-            "dir": "C:\\Users\\nutri\\projects\\ryven projects\\Ryven_mmorys\\Ryven\\ryven\\example_nodes\\linalg"
+            "name": "std",
+            "dir": "/home/leon/projects/ryven_projects/Ryven/ryven/example_nodes/std"
         }
     ],
-    "scripts": [
-        {
-            "title": "hello world",
-            "variables": {
-                "a": {
-                    "serialized": "gARLSi4="
+    "GID": 2,
+    "version": "0.4.0a12",
+    "flows": {
+        "hello world": {
+            "GID": 5,
+            "algorithm mode": "data",
+            "nodes": [
+                {
+                    "GID": 6,
+                    "identifier": "linalg.EditMatrix_Node",
+                    "version": null,
+                    "state data": "gASVMgEAAAAAAAB9lIwRZXhwcmVzc2lvbiBtYXRyaXiUWBQBAABnQVNWeEFBQUFBQUFBQUNNRlc1MWJYQjVMbU52Y21VdWJYVnNkR2xoY25KaGVaU01ERjl5WldOdmJuTjBjblZqZEpTVGxJd0ZiblZ0Y0htVWpBZHVaR0Z5Y21GNWxKT1VTd0NGbEVNQllwU0hsRktVS0VzQlN3SkxBb2FVYUFPTUJXUjBlWEJsbEpPVWpBSlZOSlNKaUllVVVwUW9Td09NQVR5VVRrNU9TeEJMQkVzSWRKUmlpVU5BTVFBQUFHb0FBQUFBQUFBQUFBQUFBREVBQUFBQUFBQUFBQUFBQUFBQUFBQXdBQUFBTFFBQUFERUFBQUJxQUFBQVlRQUFBQUFBQUFBQUFBQUFBQUFBQUpSMGxHSXWUcy4=",
+                    "additional data": {},
+                    "inputs": [],
+                    "outputs": [
+                        {
+                            "GID": 8,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {
+                            "a": "var_val_updated"
+                        }
+                    },
+                    "pos x": 214.009788256565,
+                    "pos y": 283.198189623965,
+                    "main widget data": "gASVKwAAAAAAAAB9lCiMBHRleHSUjBMgIDFqICAgIDEKMC0xaiAgICBhlIwFc2hvd26UiHUu",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "hide preview": {
+                            "method": "ac_hide_mw"
+                        }
+                    },
+                    "display title": "Matrix"
                 },
-                "mat": {
-                    "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAAAgFJAAAAAAAAAAACUdJRiLg=="
-                }
-            },
-            "flow": {
-                "algorithm mode": "data",
-                "nodes": [
-                    {
-                        "identifier": "linalg.Matrix_Node",
-                        "version": null,
-                        "state data": "gASVVAAAAAAAAAB9lCiMEm1haW4gd2lkZ2V0IGhpZGRlbpSJjBFleHByZXNzaW9uIG1hdHJpeJRdlChdlCiMAjFqlIwBMZRlXZQojAQwLTFqlIwGdignYScplGVldS4=",
-                        "additional data": {
-                            "special actions": {
-                                "hide preview": {
-                                    "method": "action_hide_mw"
-                                }
-                            },
-                            "display title": "Matrix"
-                        },
-                        "inputs": [],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 6
-                            }
-                        ],
-                        "GID": 5,
-                        "pos x": 268.009788256565,
-                        "pos y": 113.19818962396498,
-                        "main widget data": "gASVMwAAAAAAAAB9lCiMBHRleHSUjBsgICAgMWogICAgICAxCiAgMC0xaiB2KCdhJymUjAVzaG93bpSIdS4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "linalg.ShowMatrix",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "Show Matrix"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 8,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 9
-                            }
-                        ],
-                        "GID": 7,
-                        "pos x": 523.009788256565,
-                        "pos y": 119.19818962396498,
-                        "main widget data": "gASVNwAAAAAAAAB9lCiMBHRleHSUjB8gICAgIDFqICAoMSswaikKICAgIC0xaiAoNzQrMGoplIwFc2hvd26UiHUu",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.SetVar_Node",
-                        "version": null,
-                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
-                        "additional data": {
-                            "special actions": {
-                                "make active": {
-                                    "method": "action_make_active"
-                                }
+                {
+                    "GID": 12,
+                    "version": "v0.2",
+                    "identifier": "linalg.ShowMatrix",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 15,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 16,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 469.009788256565,
+                    "pos y": 289.198189623965,
+                    "main widget data": "gASVQwAAAAAAAAB9lCiMBHRleHSUjCsgICAgICAgIDFqICAgICAoMSswaikKICAgICAgIC0xaiAoMC43NzIrMGoplIwFc2hvd26UiHUu",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "hide preview": {
+                            "method": "ac_hide_mw"
+                        }
+                    },
+                    "display title": "Show Matrix"
+                },
+                {
+                    "GID": 17,
+                    "version": "v0.1",
+                    "identifier": "built_in.SetVar_Node",
+                    "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 23,
+                            "type": "data",
+                            "label": "var",
+                            "default": {
+                                "GID": 28,
+                                "identifier": "Data",
+                                "serialized": "gASVBQAAAAAAAACMAWGULg=="
                             },
-                            "display title": "set var"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "var",
-                                "GID": 11,
-                                "val": "gAROLg==",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMAWGUcy4="
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVAZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAFhlHVicy4="
+                        },
+                        {
+                            "GID": 25,
+                            "type": "data",
+                            "label": "val",
+                            "default": {
+                                "GID": 6431,
+                                "identifier": "Data",
+                                "serialized": "gASVCQAAAAAAAACMBTAuNzcylC4="
                             },
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 12,
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVEAAAAAAAAAB9lIwEdGV4dJSMAjc0lHMu"
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 13
-                            }
-                        ],
-                        "GID": 10,
-                        "pos x": 549.009788256565,
-                        "pos y": 265.198189623965,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Slider_Node",
-                        "version": null,
-                        "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/53S8an7523Mu",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "slider"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "scl",
-                                "GID": 15,
-                                "val": "gARLMi4=",
-                                "dtype": "DType.Integer",
-                                "dtype state": "gASVLAAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAIwDZG9jlIwAlIwGYm91bmRzlE51Lg==",
-                                "has widget": true,
-                                "widget data": "gASVDAAAAAAAAAB9lIwDdmFslEsycy4="
+                            "has widget": true,
+                            "widget name": "val",
+                            "widget pos": "besides",
+                            "widget data": "gASVcAAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVEZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjAUwLjc3MpR1YnMu"
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 27,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 545.009788256565,
+                    "pos y": 96.19818962396499,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "set var"
+                },
+                {
+                    "GID": 30,
+                    "version": "v0.2",
+                    "identifier": "std.Slider_Node",
+                    "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/6LQ5WBBiTnMu",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 34,
+                            "type": "data",
+                            "label": "scl",
+                            "default": {
+                                "GID": 40,
+                                "identifier": "Data",
+                                "serialized": "gARLAS4="
                             },
-                            {
-                                "type": "data",
-                                "label": "round",
-                                "GID": 16,
-                                "val": "gASILg==",
-                                "dtype": "DType.Boolean",
-                                "dtype state": "gASVKgAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROdS4=",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwHY2hlY2tlZJSIcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 17
-                            }
-                        ],
-                        "GID": 14,
-                        "pos x": 214.009788256565,
-                        "pos y": 264.198189623965,
-                        "main widget data": "gASVDQAAAAAAAAB9lIwDdmFslE3dAnMu",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Multiply_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {
-                                "add input": {
-                                    "method": "add_operand_input"
-                                },
-                                "remove input 0": {
-                                    "method": "remove_operand_input",
-                                    "data": 0
-                                },
-                                "remove input 1": {
-                                    "method": "remove_operand_input",
-                                    "data": 1
-                                }
+                            "has widget": true,
+                            "widget name": "scale",
+                            "widget pos": "besides",
+                            "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVIZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+                        },
+                        {
+                            "GID": 36,
+                            "type": "data",
+                            "label": "round",
+                            "default": {
+                                "GID": 42,
+                                "identifier": "Data",
+                                "serialized": "gASJLg=="
                             },
-                            "display title": "*"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 19,
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVEAAAAAAAAAB9lIwEdGV4dJSMAjM3lHMu"
+                            "has widget": true,
+                            "widget name": "round",
+                            "widget pos": "besides",
+                            "widget data": "gASVaQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVMZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUiXVicy4="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 38,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 210.009788256565,
+                    "pos y": 95.19818962396499,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "slider"
+                },
+                {
+                    "GID": 43,
+                    "version": "v0.2",
+                    "identifier": "std.Multiply_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 47,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 6429,
+                                "identifier": "Data",
+                                "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
                             },
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 20,
-                                "val": "gARLAi4=",
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMATKUcy4="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 21
-                            }
-                        ],
-                        "GID": 18,
-                        "pos x": 385.009788256565,
-                        "pos y": 291.198189623965,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.SetVar_Node",
-                        "version": null,
-                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
-                        "additional data": {
-                            "special actions": {
-                                "make active": {
-                                    "method": "action_make_active"
-                                }
+                            "has widget": true,
+                            "widget name": "in",
+                            "widget pos": "besides",
+                            "widget data": "gASVcQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVQZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSURz/otDlYEGJOdWJzLg=="
+                        },
+                        {
+                            "GID": 49,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 55,
+                                "identifier": "Data",
+                                "serialized": "gARLAS4="
                             },
-                            "display title": "set var"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "var",
-                                "GID": 23,
-                                "val": "gASVBwAAAAAAAACMA21hdJQu",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA21hdJRzLg=="
+                            "has widget": true,
+                            "widget name": "in",
+                            "widget pos": "besides",
+                            "widget data": "gASVagAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVUZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUSwF1YnMu"
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 51,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 381.009788256565,
+                    "pos y": 122.19818962396499,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "add input": {
+                            "method": "add_operand_input"
+                        },
+                        "remove input": {
+                            "0": {
+                                "method": "remove_operand_input",
+                                "data": 0
                             },
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 24,
-                                "dtype": "DType.Data",
-                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
-                                "has widget": true,
-                                "widget data": "gASVNAAAAAAAAAB9lIwEdGV4dJSMJltbIDAuKzEuaiAgMS4rMC5qXQogWyAwLi0xLmogNzQuKzAual1dlHMu"
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 25
-                            }
-                        ],
-                        "GID": 22,
-                        "pos x": 851.7300738632727,
-                        "pos y": 132.0220031444171,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.GetVar_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "get var"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 27,
-                                "val": "gASVBwAAAAAAAACMA21hdJQu",
-                                "dtype": "DType.String",
-                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
-                                "has widget": true,
-                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA21hdJRzLg=="
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "val",
-                                "GID": 28
-                            }
-                        ],
-                        "GID": 26,
-                        "pos x": 152.06383333061729,
-                        "pos y": 405.2129042004253,
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "linalg.InverseMatrix",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "Inverse"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 30,
-                                "has widget": false
+                            "1": {
+                                "method": "remove_operand_input",
+                                "data": 1
                             }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 31
-                            }
-                        ],
-                        "GID": 29,
-                        "pos x": 175.96624820900752,
-                        "pos y": 548.4374023593136,
-                        "main widget data": "gASVRwAAAAAAAAB9lCiMBHRleHSUjC8gICAtMC45ODY3aiAgICAgMC4wMTMzagooMC4wMTMzKzBqKSAoMC4wMTMzKzBqKZSMBXNob3dulIh1Lg==",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "linalg.DetOfMatrix",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "Determinant"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 33,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 34
-                            }
-                        ],
-                        "GID": 32,
-                        "pos x": 611.0383971650748,
-                        "pos y": 548.1962980663284,
-                        "main widget data": "gASVLgAAAAAAAAB9lCiMBHRleHSUjBYtMC4wMTMzMzMzMzMzMzMzMzMzMzhqlIwFc2hvd26UiHUu",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "linalg.SolveLEq",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "Solve"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 36,
-                                "has widget": false
+                        }
+                    },
+                    "display title": "*"
+                },
+                {
+                    "GID": 56,
+                    "version": "v0.1",
+                    "identifier": "built_in.SetVar_Node",
+                    "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 62,
+                            "type": "data",
+                            "label": "var",
+                            "default": {
+                                "GID": 67,
+                                "identifier": "Data",
+                                "serialized": "gASVBwAAAAAAAACMA21hdJQu"
                             },
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 37,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 38
-                            }
-                        ],
-                        "GID": 35,
-                        "pos x": 885.1839459089059,
-                        "pos y": 322.570155217133,
-                        "main widget data": "gASVLwAAAAAAAAB9lCiMBHRleHSUjBcgICAtMC45ODY3aiAoMC4wMTMzKzBqKZSMBXNob3dulIh1Lg==",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "linalg.MatMul",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "Mult"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 40,
-                                "has widget": false
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVYZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANtYXSUdWJzLg=="
+                        },
+                        {
+                            "GID": 64,
+                            "type": "data",
+                            "label": "val",
+                            "default": {
+                                "GID": 6435,
+                                "identifier": "Data",
+                                "serialized": "gASVMgAAAAAAAACMLltbMC4gICArMS5qIDEuICAgKzAual0KIFswLiAgIC0xLmogMC43NzIrMC5qXV2ULg=="
                             },
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 41,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 42
-                            }
-                        ],
-                        "GID": 39,
-                        "pos x": 372.26934314274683,
-                        "pos y": 442.4371649448529,
-                        "main widget data": "gASVMwAAAAAAAAB9lCiMBHRleHSUjBsoMSswaikgICAgIDBqCiAgICAwaiAoMSswaimUjAVzaG93bpSIdS4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "std.Eval_Node",
-                        "version": null,
-                        "state data": "gASVPQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMDihpbnBbMF0pWzBdWzpdlHUu",
-                        "additional data": {
-                            "special actions": {
-                                "add input": {
-                                    "method": "add_param_input"
-                                },
-                                "remove input 0": {
-                                    "method": "remove_param_input",
-                                    "data": 0
-                                }
+                            "has widget": true,
+                            "widget name": "val",
+                            "widget pos": "besides",
+                            "widget data": "gASVmQAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVcZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjC5bWzAuICAgKzEuaiAxLiAgICswLmpdCiBbMC4gICAtMS5qIDAuNzcyKzAual1dlHVicy4="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 66,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 892.7300738632725,
+                    "pos y": 198.0220031444171,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "set var"
+                },
+                {
+                    "GID": 69,
+                    "version": "v0.2",
+                    "identifier": "built_in.GetVar_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 72,
+                            "type": "data",
+                            "label": "",
+                            "default": {
+                                "GID": 76,
+                                "identifier": "Data",
+                                "serialized": "gASVBwAAAAAAAACMA21hdJQu"
                             },
-                            "display title": "eval"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 44,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 45
-                            }
-                        ],
-                        "GID": 43,
-                        "pos x": 598.3422117376416,
-                        "pos y": 418.5614824529824,
-                        "main widget data": "gASVHAAAAAAAAAB9lIwEdGV4dJSMDihpbnBbMF0pWzBdWzpdlHMu",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    },
-                    {
-                        "identifier": "built_in.Result_Node",
-                        "version": null,
-                        "state data": "gAR9lC4=",
-                        "additional data": {
-                            "special actions": {},
-                            "display title": "result"
-                        },
-                        "inputs": [
-                            {
-                                "type": "data",
-                                "label": "",
-                                "GID": 47,
-                                "has widget": false
-                            }
-                        ],
-                        "outputs": [],
-                        "GID": 46,
-                        "pos x": 882.6120564064703,
-                        "pos y": 429.0070935431757,
-                        "main widget data": "gAR9lC4=",
-                        "unconnected ports hidden": false,
-                        "collapsed": false
-                    }
-                ],
-                "connections": [
-                    {
-                        "GID": 48,
-                        "parent node index": 0,
-                        "output port index": 0,
-                        "connected node": 1,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 49,
-                        "parent node index": 1,
-                        "output port index": 0,
-                        "connected node": 5,
-                        "connected input port index": 1
-                    },
-                    {
-                        "GID": 50,
-                        "parent node index": 1,
-                        "output port index": 0,
-                        "connected node": 9,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 51,
-                        "parent node index": 3,
-                        "output port index": 0,
-                        "connected node": 4,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 52,
-                        "parent node index": 4,
-                        "output port index": 0,
-                        "connected node": 2,
-                        "connected input port index": 1
-                    },
-                    {
-                        "GID": 53,
-                        "parent node index": 6,
-                        "output port index": 0,
-                        "connected node": 10,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 54,
-                        "parent node index": 6,
-                        "output port index": 0,
-                        "connected node": 7,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 55,
-                        "parent node index": 7,
-                        "output port index": 0,
-                        "connected node": 8,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 56,
-                        "parent node index": 7,
-                        "output port index": 0,
-                        "connected node": 10,
-                        "connected input port index": 1
-                    },
-                    {
-                        "GID": 57,
-                        "parent node index": 10,
-                        "output port index": 0,
-                        "connected node": 11,
-                        "connected input port index": 0
-                    },
-                    {
-                        "GID": 58,
-                        "parent node index": 11,
-                        "output port index": 0,
-                        "connected node": 9,
-                        "connected input port index": 1
-                    },
-                    {
-                        "GID": 59,
-                        "parent node index": 11,
-                        "output port index": 0,
-                        "connected node": 12,
-                        "connected input port index": 0
-                    }
-                ],
-                "GID": 4,
-                "flow view": {
-                    "drawings": [],
-                    "view size": [
-                        6400.0,
-                        4800.0
+                            "has widget": true,
+                            "widget name": "varname",
+                            "widget pos": "besides",
+                            "widget data": "gASVbgAAAAAAAAB9lIwDdmFslIwOcnl2ZW5jb3JlLkRhdGGUjAREYXRhlJOUKYGUfZQojAlnbG9iYWxfaWSUTVgZjA5wcmV2X2dsb2JhbF9pZJROjAxwcmV2X3ZlcnNpb26UTowIX3BheWxvYWSUjANtYXSUdWJzLg=="
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 74,
+                            "type": "data",
+                            "label": "val"
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {
+                            "mat": "var_val_changed"
+                        }
+                    },
+                    "pos x": 152.06383333061729,
+                    "pos y": 405.2129042004253,
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "get var"
+                },
+                {
+                    "GID": 77,
+                    "version": "v0.2",
+                    "identifier": "linalg.InverseMatrix",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 80,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 81,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 175.96624820900752,
+                    "pos y": 548.4374023593136,
+                    "main widget data": "gASVRwAAAAAAAAB9lCiMBHRleHSUjC8gICAtMC40MzU3aiAgICAgMC41NjQzagooMC41NjQzKzBqKSAoMC41NjQzKzBqKZSMBXNob3dulIh1Lg==",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "hide preview": {
+                            "method": "ac_hide_mw"
+                        }
+                    },
+                    "display title": "Inverse"
+                },
+                {
+                    "GID": 82,
+                    "version": "v0.2",
+                    "identifier": "linalg.DetOfMatrix",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 85,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 86,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 611.0383971650748,
+                    "pos y": 548.1962980663284,
+                    "main widget data": "gASVLAAAAAAAAAB9lCiMBHRleHSUjBQtMC41NjQzMzQwODU3Nzg3ODExapSMBXNob3dulIh1Lg==",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "hide preview": {
+                            "method": "ac_hide_mw"
+                        }
+                    },
+                    "display title": "Determinant"
+                },
+                {
+                    "GID": 87,
+                    "version": "v0.2",
+                    "identifier": "linalg.SolveLEq",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 91,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        },
+                        {
+                            "GID": 92,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 93,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 885.1839459089059,
+                    "pos y": 322.570155217133,
+                    "main widget data": "gASVLwAAAAAAAAB9lCiMBHRleHSUjBcgICAtMC40MzU3aiAoMC41NjQzKzBqKZSMBXNob3dulIh1Lg==",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "hide preview": {
+                            "method": "ac_hide_mw"
+                        }
+                    },
+                    "display title": "Solve"
+                },
+                {
+                    "GID": 94,
+                    "version": "v0.2",
+                    "identifier": "linalg.MatMul",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 98,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        },
+                        {
+                            "GID": 99,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 100,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 361.26934314274683,
+                    "pos y": 439.43716494485284,
+                    "main widget data": "gASVMwAAAAAAAAB9lCiMBHRleHSUjBsoMSswaikgICAgIDBqCiAgICAwaiAoMSswaimUjAVzaG93bpSIdS4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "hide preview": {
+                            "method": "ac_hide_mw"
+                        }
+                    },
+                    "display title": "Mult"
+                },
+                {
+                    "GID": 101,
+                    "version": "v0.2",
+                    "identifier": "std.Eval_Node",
+                    "state data": "gASVPQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMDihpbnBbMF0pWzBdWzpdlHUu",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 103,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [
+                        {
+                            "GID": 104,
+                            "type": "data",
+                            "label": ""
+                        }
+                    ],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 598.3422117376416,
+                    "pos y": 418.5614824529824,
+                    "main widget data": "gASVHAAAAAAAAAB9lIwEdGV4dJSMDihpbnBbMF0pWzBdWzpdlHMu",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        },
+                        "add input": {
+                            "method": "add_input"
+                        },
+                        "remove input": {}
+                    },
+                    "display title": "eval"
+                },
+                {
+                    "GID": 105,
+                    "version": "v0.2",
+                    "identifier": "built_in.Result_Node",
+                    "state data": "gAR9lC4=",
+                    "additional data": {},
+                    "inputs": [
+                        {
+                            "GID": 107,
+                            "type": "data",
+                            "label": "",
+                            "has widget": false
+                        }
+                    ],
+                    "outputs": [],
+                    "Variables": {
+                        "subscriptions": {}
+                    },
+                    "pos x": 882.6120564064703,
+                    "pos y": 429.0070935431757,
+                    "main widget data": "gAR9lC4=",
+                    "unconnected ports hidden": false,
+                    "collapsed": false,
+                    "actions": {
+                        "update shape": {
+                            "method": "update_shape"
+                        },
+                        "hide unconnected ports": {
+                            "method": "hide_unconnected_ports"
+                        },
+                        "change title": {
+                            "method": "change_title"
+                        },
+                        "console ref": {
+                            "method": "console_ref_monkeypatch"
+                        }
+                    },
+                    "display title": "result"
+                }
+            ],
+            "connections": [
+                {
+                    "parent node index": 0,
+                    "output port index": 0,
+                    "connected node": 1,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 1,
+                    "output port index": 0,
+                    "connected node": 5,
+                    "connected input port index": 1
+                },
+                {
+                    "parent node index": 1,
+                    "output port index": 0,
+                    "connected node": 9,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 3,
+                    "output port index": 0,
+                    "connected node": 4,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 4,
+                    "output port index": 0,
+                    "connected node": 2,
+                    "connected input port index": 1
+                },
+                {
+                    "parent node index": 6,
+                    "output port index": 0,
+                    "connected node": 10,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 6,
+                    "output port index": 0,
+                    "connected node": 7,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 7,
+                    "output port index": 0,
+                    "connected node": 8,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 7,
+                    "output port index": 0,
+                    "connected node": 10,
+                    "connected input port index": 1
+                },
+                {
+                    "parent node index": 10,
+                    "output port index": 0,
+                    "connected node": 11,
+                    "connected input port index": 0
+                },
+                {
+                    "parent node index": 11,
+                    "output port index": 0,
+                    "connected node": 9,
+                    "connected input port index": 1
+                },
+                {
+                    "parent node index": 11,
+                    "output port index": 0,
+                    "connected node": 12,
+                    "connected input port index": 0
+                }
+            ],
+            "output data": [
+                {
+                    "data": {
+                        "GID": 6456,
+                        "identifier": "MatrixData",
+                        "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+                    },
+                    "dependent node outputs": [
+                        0,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6457,
+                        "identifier": "MatrixData",
+                        "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+                    },
+                    "dependent node outputs": [
+                        1,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6428,
+                        "identifier": "Data",
+                        "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+                    },
+                    "dependent node outputs": [
+                        3,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6430,
+                        "identifier": "Data",
+                        "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+                    },
+                    "dependent node outputs": [
+                        4,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6468,
+                        "identifier": "Data",
+                        "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+                    },
+                    "dependent node outputs": [
+                        6,
+                        0
                     ]
+                },
+                {
+                    "data": {
+                        "GID": 6472,
+                        "identifier": "MatrixData",
+                        "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAEPljSfPh278AAAAAAAAAAHgDTlsGD+I/eANOWwYP4j8AAAAAAAAAAHgDTlsGD+I/AAAAAAAAAACUdJRiLg=="
+                    },
+                    "dependent node outputs": [
+                        7,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6473,
+                        "identifier": "MatrixData",
+                        "serialized": "gASVcgAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMBnNjYWxhcpSTlIwFbnVtcHmUjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGJDEAAAAAAAAAAAeANOWwYP4r+UhpRSlC4="
+                    },
+                    "dependent node outputs": [
+                        8,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6479,
+                        "identifier": "MatrixData",
+                        "serialized": "gASVqQAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwKFlGgDjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGKJQyAAAAAAAAAAABD5Y0nz4du/eANOWwYP4j8AAAAAAAAAAJR0lGIu"
+                    },
+                    "dependent node outputs": [
+                        9,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6474,
+                        "identifier": "MatrixData",
+                        "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkP6YJq2umDwAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAACUdJRiLg=="
+                    },
+                    "dependent node outputs": [
+                        10,
+                        0
+                    ]
+                },
+                {
+                    "data": {
+                        "GID": 6478,
+                        "identifier": "Data",
+                        "serialized": "gASVqQAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwKFlGgDjAVkdHlwZZSTlIwDYzE2lImIh5RSlChLA4wBPJROTk5K/////0r/////SwB0lGKJQyAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJR0lGIu"
+                    },
+                    "dependent node outputs": [
+                        11,
+                        0
+                    ]
+                }
+            ],
+            "flow view": {
+                "drawings": [],
+                "view size": [
+                    10000.0,
+                    7000.0
+                ]
+            }
+        }
+    },
+    "addons": {
+        "Variables": {
+            "GID": 3,
+            "version": "0.4",
+            "custom state": {
+                "5": {
+                    "a": {
+                        "GID": 6432,
+                        "identifier": "Data",
+                        "serialized": "gASVCgAAAAAAAABHP+i0OVgQYk4u"
+                    },
+                    "mat": {
+                        "GID": 6458,
+                        "identifier": "Data",
+                        "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwv05iEFg5tOg/AAAAAAAAAACUdJRiLg=="
+                    }
                 }
-            },
-            "GID": 1
+            }
+        },
+        "Logging": {
+            "GID": 4,
+            "version": "0.0.1",
+            "custom state": {}
         }
-    ]
+    }
 }
\ No newline at end of file
diff --git a/ryven/examples_projects/matrices_OLD.json b/ryven/examples_projects/matrices_OLD.json
new file mode 100644
index 00000000..eb8328ed
--- /dev/null
+++ b/ryven/examples_projects/matrices_OLD.json
@@ -0,0 +1,602 @@
+{
+    "general info": {
+        "type": "Ryven project file"
+    },
+    "required packages": [
+        {
+            "name": "std",
+            "dir": "C:\\Users\\nutri\\projects\\ryven projects\\Ryven_mmorys\\Ryven\\ryven\\example_nodes\\std"
+        },
+        {
+            "name": "linalg",
+            "dir": "C:\\Users\\nutri\\projects\\ryven projects\\Ryven_mmorys\\Ryven\\ryven\\example_nodes\\linalg"
+        }
+    ],
+    "scripts": [
+        {
+            "title": "hello world",
+            "variables": {
+                "a": {
+                    "serialized": "gARLSi4="
+                },
+                "mat": {
+                    "serialized": "gASVywAAAAAAAACMFW51bXB5LmNvcmUubXVsdGlhcnJheZSMDF9yZWNvbnN0cnVjdJSTlIwFbnVtcHmUjAduZGFycmF5lJOUSwCFlEMBYpSHlFKUKEsBSwJLAoaUaAOMBWR0eXBllJOUjANjMTaUiYiHlFKUKEsDjAE8lE5OTkr/////Sv////9LAHSUYolDQAAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwvwAAAAAAgFJAAAAAAAAAAACUdJRiLg=="
+                }
+            },
+            "flow": {
+                "algorithm mode": "data",
+                "nodes": [
+                    {
+                        "identifier": "linalg.Matrix_Node",
+                        "version": null,
+                        "state data": "gASVVAAAAAAAAAB9lCiMEm1haW4gd2lkZ2V0IGhpZGRlbpSJjBFleHByZXNzaW9uIG1hdHJpeJRdlChdlCiMAjFqlIwBMZRlXZQojAQwLTFqlIwGdignYScplGVldS4=",
+                        "additional data": {
+                            "special actions": {
+                                "hide preview": {
+                                    "method": "action_hide_mw"
+                                }
+                            },
+                            "display title": "Matrix"
+                        },
+                        "inputs": [],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 6
+                            }
+                        ],
+                        "GID": 5,
+                        "pos x": 268.009788256565,
+                        "pos y": 113.19818962396498,
+                        "main widget data": "gASVMwAAAAAAAAB9lCiMBHRleHSUjBsgICAgMWogICAgICAxCiAgMC0xaiB2KCdhJymUjAVzaG93bpSIdS4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "linalg.ShowMatrix",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "Show Matrix"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 8,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 9
+                            }
+                        ],
+                        "GID": 7,
+                        "pos x": 523.009788256565,
+                        "pos y": 119.19818962396498,
+                        "main widget data": "gASVNwAAAAAAAAB9lCiMBHRleHSUjB8gICAgIDFqICAoMSswaikKICAgIC0xaiAoNzQrMGoplIwFc2hvd26UiHUu",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.SetVar_Node",
+                        "version": null,
+                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
+                        "additional data": {
+                            "special actions": {
+                                "make active": {
+                                    "method": "action_make_active"
+                                }
+                            },
+                            "display title": "set var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "var",
+                                "GID": 11,
+                                "val": "gAROLg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMAWGUcy4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 12,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVEAAAAAAAAAB9lIwEdGV4dJSMAjc0lHMu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 13
+                            }
+                        ],
+                        "GID": 10,
+                        "pos x": 549.009788256565,
+                        "pos y": 265.198189623965,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Slider_Node",
+                        "version": null,
+                        "state data": "gASVEwAAAAAAAAB9lIwDdmFslEc/53S8an7523Mu",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "slider"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "scl",
+                                "GID": 15,
+                                "val": "gARLMi4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASVLAAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAIwDZG9jlIwAlIwGYm91bmRzlE51Lg==",
+                                "has widget": true,
+                                "widget data": "gASVDAAAAAAAAAB9lIwDdmFslEsycy4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "round",
+                                "GID": 16,
+                                "val": "gASILg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASVKgAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROdS4=",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwHY2hlY2tlZJSIcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 17
+                            }
+                        ],
+                        "GID": 14,
+                        "pos x": 214.009788256565,
+                        "pos y": 264.198189623965,
+                        "main widget data": "gASVDQAAAAAAAAB9lIwDdmFslE3dAnMu",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Multiply_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {
+                                "add input": {
+                                    "method": "add_operand_input"
+                                },
+                                "remove input 0": {
+                                    "method": "remove_operand_input",
+                                    "data": 0
+                                },
+                                "remove input 1": {
+                                    "method": "remove_operand_input",
+                                    "data": 1
+                                }
+                            },
+                            "display title": "*"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 19,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVEAAAAAAAAAB9lIwEdGV4dJSMAjM3lHMu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 20,
+                                "val": "gARLAi4=",
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVDwAAAAAAAAB9lIwEdGV4dJSMATKUcy4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 21
+                            }
+                        ],
+                        "GID": 18,
+                        "pos x": 385.009788256565,
+                        "pos y": 291.198189623965,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.SetVar_Node",
+                        "version": null,
+                        "state data": "gASVDgAAAAAAAAB9lIwGYWN0aXZllIlzLg==",
+                        "additional data": {
+                            "special actions": {
+                                "make active": {
+                                    "method": "action_make_active"
+                                }
+                            },
+                            "display title": "set var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "var",
+                                "GID": 23,
+                                "val": "gASVBwAAAAAAAACMA21hdJQu",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA21hdJRzLg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 24,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVNQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjARzaXpllIwBc5R1Lg==",
+                                "has widget": true,
+                                "widget data": "gASVNAAAAAAAAAB9lIwEdGV4dJSMJltbIDAuKzEuaiAgMS4rMC5qXQogWyAwLi0xLmogNzQuKzAual1dlHMu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 25
+                            }
+                        ],
+                        "GID": 22,
+                        "pos x": 851.7300738632727,
+                        "pos y": 132.0220031444171,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.GetVar_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "get var"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 27,
+                                "val": "gASVBwAAAAAAAACMA21hdJQu",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVNwAAAAAAAAB9lCiMB2RlZmF1bHSUjACUjAN2YWyUaAKMA2RvY5RoAowGYm91bmRzlE6MBHNpemWUjAFtlHUu",
+                                "has widget": true,
+                                "widget data": "gASVEQAAAAAAAAB9lIwEdGV4dJSMA21hdJRzLg=="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "val",
+                                "GID": 28
+                            }
+                        ],
+                        "GID": 26,
+                        "pos x": 152.06383333061729,
+                        "pos y": 405.2129042004253,
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "linalg.InverseMatrix",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "Inverse"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 30,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 31
+                            }
+                        ],
+                        "GID": 29,
+                        "pos x": 175.96624820900752,
+                        "pos y": 548.4374023593136,
+                        "main widget data": "gASVRwAAAAAAAAB9lCiMBHRleHSUjC8gICAtMC45ODY3aiAgICAgMC4wMTMzagooMC4wMTMzKzBqKSAoMC4wMTMzKzBqKZSMBXNob3dulIh1Lg==",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "linalg.DetOfMatrix",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "Determinant"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 33,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 34
+                            }
+                        ],
+                        "GID": 32,
+                        "pos x": 611.0383971650748,
+                        "pos y": 548.1962980663284,
+                        "main widget data": "gASVLgAAAAAAAAB9lCiMBHRleHSUjBYtMC4wMTMzMzMzMzMzMzMzMzMzMzhqlIwFc2hvd26UiHUu",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "linalg.SolveLEq",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "Solve"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 36,
+                                "has widget": false
+                            },
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 37,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 38
+                            }
+                        ],
+                        "GID": 35,
+                        "pos x": 885.1839459089059,
+                        "pos y": 322.570155217133,
+                        "main widget data": "gASVLwAAAAAAAAB9lCiMBHRleHSUjBcgICAtMC45ODY3aiAoMC4wMTMzKzBqKZSMBXNob3dulIh1Lg==",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "linalg.MatMul",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "Mult"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 40,
+                                "has widget": false
+                            },
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 41,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 42
+                            }
+                        ],
+                        "GID": 39,
+                        "pos x": 372.26934314274683,
+                        "pos y": 442.4371649448529,
+                        "main widget data": "gASVMwAAAAAAAAB9lCiMBHRleHSUjBsoMSswaikgICAgIDBqCiAgICAwaiAoMSswaimUjAVzaG93bpSIdS4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "std.Eval_Node",
+                        "version": null,
+                        "state data": "gASVPQAAAAAAAAB9lCiMEG51bSBwYXJhbSBpbnB1dHOUSwGMD2V4cHJlc3Npb24gY29kZZSMDihpbnBbMF0pWzBdWzpdlHUu",
+                        "additional data": {
+                            "special actions": {
+                                "add input": {
+                                    "method": "add_param_input"
+                                },
+                                "remove input 0": {
+                                    "method": "remove_param_input",
+                                    "data": 0
+                                }
+                            },
+                            "display title": "eval"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 44,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 45
+                            }
+                        ],
+                        "GID": 43,
+                        "pos x": 598.3422117376416,
+                        "pos y": 418.5614824529824,
+                        "main widget data": "gASVHAAAAAAAAAB9lIwEdGV4dJSMDihpbnBbMF0pWzBdWzpdlHMu",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    },
+                    {
+                        "identifier": "built_in.Result_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {
+                            "special actions": {},
+                            "display title": "result"
+                        },
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "",
+                                "GID": 47,
+                                "has widget": false
+                            }
+                        ],
+                        "outputs": [],
+                        "GID": 46,
+                        "pos x": 882.6120564064703,
+                        "pos y": 429.0070935431757,
+                        "main widget data": "gAR9lC4=",
+                        "unconnected ports hidden": false,
+                        "collapsed": false
+                    }
+                ],
+                "connections": [
+                    {
+                        "GID": 48,
+                        "parent node index": 0,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 49,
+                        "parent node index": 1,
+                        "output port index": 0,
+                        "connected node": 5,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 50,
+                        "parent node index": 1,
+                        "output port index": 0,
+                        "connected node": 9,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 51,
+                        "parent node index": 3,
+                        "output port index": 0,
+                        "connected node": 4,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 52,
+                        "parent node index": 4,
+                        "output port index": 0,
+                        "connected node": 2,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 53,
+                        "parent node index": 6,
+                        "output port index": 0,
+                        "connected node": 10,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 54,
+                        "parent node index": 6,
+                        "output port index": 0,
+                        "connected node": 7,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 55,
+                        "parent node index": 7,
+                        "output port index": 0,
+                        "connected node": 8,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 56,
+                        "parent node index": 7,
+                        "output port index": 0,
+                        "connected node": 10,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 57,
+                        "parent node index": 10,
+                        "output port index": 0,
+                        "connected node": 11,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 58,
+                        "parent node index": 11,
+                        "output port index": 0,
+                        "connected node": 9,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 59,
+                        "parent node index": 11,
+                        "output port index": 0,
+                        "connected node": 12,
+                        "connected input port index": 0
+                    }
+                ],
+                "GID": 4,
+                "flow view": {
+                    "drawings": [],
+                    "view size": [
+                        6400.0,
+                        4800.0
+                    ]
+                }
+            },
+            "GID": 1
+        }
+    ]
+}
\ No newline at end of file
diff --git a/ryven/gui/code_editor/CodeEditorWidget.py b/ryven/gui/code_editor/CodeEditorWidget.py
index 67fb3220..91eceb09 100644
--- a/ryven/gui/code_editor/CodeEditorWidget.py
+++ b/ryven/gui/code_editor/CodeEditorWidget.py
@@ -36,6 +36,11 @@ def __init__(self, theme, highlight=True, enabled=False):
         else:
             self.formatter = get_formatter_by_name('html', noclasses=True, style=LightStyle)
 
+        if self.editing:
+            self.enable_editing()
+        else:
+            self.disable_editing()
+
     def enable_editing(self):
         self.editing = True
         self.setReadOnly(False)
diff --git a/ryven/gui/code_editor/CodePreviewWidget.py b/ryven/gui/code_editor/CodePreviewWidget.py
index b37de7a5..aee8e04c 100644
--- a/ryven/gui/code_editor/CodePreviewWidget.py
+++ b/ryven/gui/code_editor/CodePreviewWidget.py
@@ -1,47 +1,40 @@
+from dataclasses import dataclass
+from typing import Type, Optional
+
 from qtpy.QtCore import Qt
 from qtpy.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QRadioButton, QLabel, QCheckBox, QGridLayout, \
     QPushButton
+from ryvencore import Node
 
 from ryven.gui.code_editor.EditSrcCodeInfoDialog import EditSrcCodeInfoDialog
 from ryven.gui.code_editor.CodeEditorWidget import CodeEditorWidget
 from ryven.gui.code_editor.SourceCodeUpdater import SrcCodeUpdater
+from ryven.gui.code_editor.codes_storage import class_codes, mod_codes, modif_codes, NodeTypeCodes, \
+    Inspectable, NodeInspectable, MainWidgetInspectable, CustomInputWidgetInspectable
 
 
 class LinkedRadioButton(QRadioButton):
-    """Represents a button linked to one particular"""
+    """Represents a button linked to one particular inspectable object."""
 
-    def __init__(self, name, node, obj):
+    def __init__(self, name, obj: Inspectable):
         super().__init__(name)
-
-        self.node = node
-        self.obj = obj
+        self.representing: Inspectable = obj
 
 
 class CodePreviewWidget(QWidget):
-    def __init__(self, main_window, flow):
-        super(CodePreviewWidget, self).__init__()
-
-        self.codes = {
-            # structure:
-            #
-            #   <node>: {
-            #       <object>: {                 (the node instance or some widget instance)
-            #           'title': str,           (for faster widget building)
-            #           'original': str,        (original code comes from Node.__class_codes__)
-            #           'edited': str(None),    (initially None)
-            #       }
-            #   }
-        }
-        self.node = None            # currently displayed node
-        self.current_obj = None     # reference to the currently shown/edited object
-        self.radio_buttons = []     # the radio buttons to select a source
-
-        flow.nodes_selection_changed.connect(self.set_selected_nodes)
+    def __init__(self, main_window, flow_view):
+        super().__init__()
 
+        self.edits_enabled = main_window.config.src_code_edits_enabled
+        self.current_insp: Optional[Inspectable] = None
+
+        # widgets
+        self.radio_buttons = []
         self.text_edit = CodeEditorWidget(main_window.theme)
-        self.setup_ui()
 
-        self.set_node(None)
+        self.setup_ui()
+        self._set_node(None)
+        flow_view.nodes_selection_changed.connect(self.set_selected_nodes)
 
     def setup_ui(self):
 
@@ -55,30 +48,31 @@ def setup_ui(self):
         # secondary_layout.setAlignment(self.class_selection_layout, Qt.AlignLeft)
 
         # edit source code buttons
-        self.edit_code_button = QPushButton('edit')
-        self.edit_code_button.setProperty('class', 'small_button')
-        self.edit_code_button.setMaximumWidth(100)
-        self.edit_code_button.clicked.connect(self.edit_code_button_clicked)
-        self.override_code_button = QPushButton('override')
-        self.override_code_button.setProperty('class', 'small_button')
-        self.override_code_button.setMaximumWidth(100)
-        self.override_code_button.setEnabled(False)
-        self.override_code_button.clicked.connect(self.override_code_button_clicked)
-        self.reset_code_button = QPushButton('reset')
-        self.reset_code_button.setProperty('class', 'small_button')
-        self.reset_code_button.setMaximumWidth(206)
-        self.reset_code_button.setEnabled(False)
-        self.reset_code_button.clicked.connect(self.reset_code_button_clicked)
-
-        edit_buttons_layout = QHBoxLayout()
-        # edit_buttons_layout.addWidget(self.highlight_code_button)
-        edit_buttons_layout.addWidget(self.edit_code_button)
-        edit_buttons_layout.addWidget(self.override_code_button)
-        edit_buttons_layout.addWidget(self.reset_code_button)
-        # edit_buttons_layout.addWidget(self.highlight_code_button)
-
-        secondary_layout.addLayout(edit_buttons_layout)
-        edit_buttons_layout.setAlignment(Qt.AlignRight)
+        if self.edits_enabled:
+            self.edit_code_button = QPushButton('edit')
+            self.edit_code_button.setProperty('class', 'small_button')
+            self.edit_code_button.setMaximumWidth(100)
+            self.edit_code_button.clicked.connect(self._edit_code_button_clicked)
+            self.override_code_button = QPushButton('override')
+            self.override_code_button.setProperty('class', 'small_button')
+            self.override_code_button.setMaximumWidth(100)
+            self.override_code_button.setEnabled(False)
+            self.override_code_button.clicked.connect(self._override_code_button_clicked)
+            self.reset_code_button = QPushButton('reset')
+            self.reset_code_button.setProperty('class', 'small_button')
+            self.reset_code_button.setMaximumWidth(206)
+            self.reset_code_button.setEnabled(False)
+            self.reset_code_button.clicked.connect(self._reset_code_button_clicked)
+
+            edit_buttons_layout = QHBoxLayout()
+            # edit_buttons_layout.addWidget(self.highlight_code_button)
+            edit_buttons_layout.addWidget(self.edit_code_button)
+            edit_buttons_layout.addWidget(self.override_code_button)
+            edit_buttons_layout.addWidget(self.reset_code_button)
+            # edit_buttons_layout.addWidget(self.highlight_code_button)
+
+            secondary_layout.addLayout(edit_buttons_layout)
+            edit_buttons_layout.setAlignment(Qt.AlignRight)
 
         main_layout = QVBoxLayout()
         main_layout.addLayout(secondary_layout)
@@ -87,113 +81,87 @@ def setup_ui(self):
 
     def set_selected_nodes(self, nodes):
         if len(nodes) == 0:
-            self.set_node(None)
+            self._set_node(None)
         else:
-            self.set_node(nodes[-1])
-
-    def set_node(self, node):
+            self._set_node(nodes[-1])
 
+    def _set_node(self, node: Optional[Node]):
         self.node = node
 
-        if node is None or node.__class_codes__ is None:  # no node selected / only imported nodes have __class_codes__
+        if node is None:
             # clear view
+            if self.edits_enabled:
+                self.edit_code_button.setEnabled(False)
+                self.override_code_button.setEnabled(False)
+                self.reset_code_button.setEnabled(False)
             self.text_edit.set_code('')
-            self.edit_code_button.setEnabled(False)
-            self.override_code_button.setEnabled(False)
-            self.reset_code_button.setEnabled(False)
-            self.clear_class_layout()
+            self._clear_class_layout()
             return
 
+        self._rebuild_class_selection(node)
 
-        if node not in self.codes:  # node not yet registered
-
-            # save class sources for all objects (node, main_widget, custom input widgets)
-
-            # saving it in this structure enables easy view updates and src code overrides
-
-            node_objects = {
-                node: {
-                    'title': 'node',
-                    'original cls': node.__class_codes__['node cls'],
-                    'original mod': node.__class_codes__['node mod'],
-                    'modified cls': None,
-                },
-            }
-
-            if node.main_widget():
-                node_objects[node.main_widget()] = {
-                    'title': 'main widget',
-                    'original cls': node.__class_codes__['main widget cls'],
-                    'original mod': node.__class_codes__['main widget mod'],
-                    'modified cls': None
-                }
-
-            for i in range(len(node.inputs)):
-                iw = node.item.inputs[i].widget
-                if iw:
-                    # find code
-                    code = ''
-                    for name, cls in node.input_widget_classes.items():
-                        if cls == iw.__class__:
-                            code = node.__class_codes__['custom input widgets'][name]['cls']
-                            break
-
-                    else:  # no break -> no custom widget (builtin widget) -> ignore
-                        continue
-
-                    node_objects[iw] = {
-                        'title': f'inp {i}',
-                        'original cls': code,
-                        'modified cls': None,
-                    }
+        code = class_codes[node.__class__].node_cls
 
-            self.codes[node] = node_objects
+        if self.edits_enabled and node in modif_codes:
+            code = modif_codes[node]
 
-        self.rebuild_class_selection(node)
-        self.update_edit_statuses()
+        self._update_code(NodeInspectable(node, code))
 
-        self.edit_code_button.setEnabled(True)
 
-        self.update_code(node, node)
+    def _update_code(self, insp: Inspectable):
+        if self.edits_enabled:
+            self._disable_editing()
+            self._update_radio_buttons_edit_status()
+            self.edit_code_button.setEnabled(True)
+            self.reset_code_button.setEnabled(insp.obj in modif_codes)
 
-    def update_code(self, node, obj):
-        """
-        Updates the 'modified' field in the nodes dict
-        """
-
-        self.disable_editing()
         self.text_edit.disable_highlighting()
 
-        self.current_obj = obj
-
-        orig = self.codes[node][obj]['original cls']
-        modf = self.codes[node][obj]['modified cls']
-
-        if modf:
-            code = modf
-            self.reset_code_button.setEnabled(True)
-        else:
-            code = orig
-            self.reset_code_button.setEnabled(False)
+        self.current_insp = insp
+        self.text_edit.set_code(insp.code)
 
-        self.text_edit.set_code(code)
-
-    def rebuild_class_selection(self, node):
-
-        self.clear_class_layout()
 
+    def _rebuild_class_selection(self, node: Node):
+        self._clear_class_layout()
         self.radio_buttons.clear()
 
-        for obj, d in self.codes[node].items():
-            rb = LinkedRadioButton(d['title'], node, obj)
-            rb.toggled.connect(self.class_RB_toggled)
-            self.class_selection_layout.addWidget(rb)
-            self.radio_buttons.append(rb)
+        codes: NodeTypeCodes = class_codes[node.__class__]
+
+        def register_rb(rb: QRadioButton):
+           rb.toggled.connect(self._class_rb_toggled)
+           self.class_selection_layout.addWidget(rb)
+           self.radio_buttons.append(rb)
+
+        # node radio button
+        code = codes.node_cls
+        code = modif_codes.get(node, code)
+        register_rb(LinkedRadioButton('node', NodeInspectable(node, code)))
+
+        # main widget radio button
+        if codes.main_widget_cls is not None:
+            mw = node.gui.main_widget()
+            code = codes.main_widget_cls
+            code = modif_codes.get(mw, code)
+            register_rb(LinkedRadioButton(
+                'main widget',
+                MainWidgetInspectable(node, node.gui.main_widget(), code)
+            ))
+
+        # custom input widgets
+        for i in range(len(node.inputs)):
+            inp = node.inputs[i]
+            if inp in node.gui.input_widgets:
+                name = node.gui.input_widgets[inp]['name']
+                widget = node.gui.item.inputs[i].widget
+                code = codes.custom_input_widget_clss[name]
+                code = modif_codes.get(widget, code)
+                register_rb(LinkedRadioButton(
+                    f'input {i}', CustomInputWidgetInspectable(node, widget, code)
+                ))
 
         self.radio_buttons[0].setChecked(True)
 
-    def clear_class_layout(self):
-
+    def _clear_class_layout(self):
         # clear layout
         for i in range(self.class_selection_layout.count()):
             item = self.class_selection_layout.itemAt(0)
@@ -201,69 +169,79 @@ def clear_class_layout(self):
             widget.hide()
             self.class_selection_layout.removeItem(item)
 
-    def update_edit_statuses(self):
-        """
-        Draws radio buttons referring to modified objects bold
-        """
+    def _update_radio_buttons_edit_status(self):
+        """Draws radio buttons referring to modified objects bold."""
 
-        for b in self.radio_buttons:
-            if self.codes[b.node][b.obj]['modified cls']:
+        for br in self.radio_buttons:
+            if modif_codes.get(br.representing.obj) is not None:
                 # o.setStyleSheet('color: #3B9CD9;')
-                f = b.font()
+                f = br.font()
                 f.setBold(True)
-                b.setFont(f)
+                br.setFont(f)
             else:
                 # o.setStyleSheet('color: white;')
-                f = b.font()
+                f = br.font()
                 f.setBold(False)
-                b.setFont(f)
+                br.setFont(f)
 
-    def class_RB_toggled(self, checked):
+    def _class_rb_toggled(self, checked):
         if checked:
-            self.update_code(self.sender().node, self.sender().obj)
+            rb: LinkedRadioButton = self.sender()
+            self._update_code(rb.representing)
 
-    def edit_code_button_clicked(self):
+    def _edit_code_button_clicked(self):
         if not EditSrcCodeInfoDialog.dont_show_again:
             info_dialog = EditSrcCodeInfoDialog(self)
             accepted = info_dialog.exec_()
             if not accepted:
                 return
-        self.enable_editing()
+        self._enable_editing()
 
-    def enable_editing(self):
+    def _enable_editing(self):
         self.text_edit.enable_editing()
         self.override_code_button.setEnabled(True)
 
-    def disable_editing(self):
+    def _disable_editing(self):
         self.text_edit.disable_editing()
         self.override_code_button.setEnabled(False)
 
-    def override_code_button_clicked(self):
-
+    def _override_code_button_clicked(self):
         new_code = self.text_edit.get_code()
-
-        SrcCodeUpdater.override_code(
-            obj=self.current_obj,
+        err = SrcCodeUpdater.override_code(
+            obj=self.current_insp.obj,
             new_class_src=new_code,
         )
-
-        self.codes[self.node][self.current_obj]['modified cls'] = new_code
-
-        self.disable_editing()
-
-        self.reset_code_button.setEnabled(True)
-        self.update_edit_statuses()
-
-    def reset_code_button_clicked(self):
-
-        code = self.codes[self.node][self.current_obj]['original cls']
-
-        SrcCodeUpdater.override_code(
-            obj=self.current_obj,
-            new_class_src=code
+        if err is None:
+            self.current_insp.code = new_code
+            modif_codes[self.current_insp.obj] = new_code
+            self._disable_editing()
+            self.reset_code_button.setEnabled(True)
+            self._update_radio_buttons_edit_status()
+        else:
+            # TODO: show error message
+            ...
+
+    def _reset_code_button_clicked(self):
+
+        insp = self.current_insp
+        o = insp.obj
+        orig_code = ''
+        if isinstance(insp, NodeInspectable):
+            orig_code = class_codes[o.__class__].node_cls
+        elif isinstance(insp, MainWidgetInspectable):
+            orig_code = class_codes[o.__class__].main_widget_cls
+        elif isinstance(insp, CustomInputWidgetInspectable):
+            orig_code = class_codes[o.__class__].custom_input_widget_clss[o.__class__]
+
+        err = SrcCodeUpdater.override_code(
+            obj=self.current_insp.obj,
+            new_class_src=orig_code
         )
 
-        self.codes[self.node][self.current_obj]['modified cls'] = None
-
-        self.update_code(self.node, self.current_obj)
-        self.update_edit_statuses()
+        if err is None:
+            self.current_insp.code = orig_code
+            del modif_codes[self.current_insp.obj]
+            self._update_code(self.current_insp)
+        else:
+            # TODO: show error message
+            ...
diff --git a/ryven/gui/code_editor/SourceCodeUpdater.py b/ryven/gui/code_editor/SourceCodeUpdater.py
index 411d4df6..d84d5cab 100644
--- a/ryven/gui/code_editor/SourceCodeUpdater.py
+++ b/ryven/gui/code_editor/SourceCodeUpdater.py
@@ -1,85 +1,63 @@
 import types
-import traceback
+from typing import Union
 
 
 def get_method_funcs(cls_def_str: str, obj):
     """
-    Returns a dict with functions under their names parsed from the methods in the class definition string using ast.
+    Parses the class definition string and returns a dict with python functions under their names,
+    parsed from the methods in the class definition string using the ast module.
     """
 
-    # requires Python >= 3.9
+    import sys
+    if sys.version_info < (3, 9):
+        raise Exception('src code modifications are only supported on Python >=3.9')
+
+    # requires Python >= 3.9 for ast.unparse()
     import ast
 
-    # extracting the functions
-    ast_funcs: [ast.FunctionDef] = [f for f in ast.parse(cls_def_str).body[0].body if type(f) == ast.FunctionDef]
+    # extract functions
+    ast_funcs: [ast.FunctionDef] = [
+        f
+        for f in ast.parse(cls_def_str).body[0].body
+        if type(f) == ast.FunctionDef
+    ]
 
     funcs = {}
     for astf in ast_funcs:
         d = __builtins__.copy()  # important: provide builtins when parsing the function
-
-        try:
-            exec(ast.unparse(astf), d)  # parse
-        except AttributeError as e:     # ast.unparse() was introduced in Python 3.9
-            print(traceback.format_exc())
-            print('\n\nNOTICE: src code override only works on Python version 3.9+, '
-                  'so you may have to update to 3.9 to use it')
-            return None
-
+        exec(ast.unparse(astf), d)
         f = d[astf.name]
-
         # # add locals scope of the object to the function
         # f.__dict__ = obj.
 
-        # add new function to the list, identified by its name
-        funcs = {
-            **funcs,
-            astf.name: f  # d[astf.name]
-        }
+        funcs[astf.name] = f
 
     return funcs
 
 
 class SrcCodeUpdater:
     """
-    Provides functionality to override method implementations of an object
+    Provides functionality to override method implementations of an inspectable object.
     """
 
     @staticmethod
-    def override_code(obj: object, new_class_src):
-
-        funcs = get_method_funcs(new_class_src, obj)
-
-        if funcs is None:
-            return
-
-        for name, f in funcs.items():  # override all methods
-            setattr(obj, name, types.MethodType(f, obj))
-            # types.MethodType() creates a bound method, bound to obj, from the function f
-
-    # -----------------------------------------------------------------------
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+    def override_code(obj: object, new_class_src) -> Union[None, Exception]:
+        try:
+            funcs = get_method_funcs(new_class_src, obj)
+            for name, f in funcs.items():  # override all methods
+                setattr(obj, name, types.MethodType(f, obj))
+                # types.MethodType() creates a method bound to obj, from the function f
+        except Exception as e:
+            return e
 
 
+    #
+    # below is the old implementation, for documentation purposes only; see discussion below
+    #
 
 
     @staticmethod
     def override_code_OLD(obj: object, orig_class_src, orig_mod_src, new_class_src):
-        """
-        ONLY FOR DOCUMENTATION: THIS IS THE OLD IMPLEMENTATION, SEE DISCUSSION BELOW
-        """
 
         import inspect
 
diff --git a/ryven/gui/code_editor/codes_storage.py b/ryven/gui/code_editor/codes_storage.py
new file mode 100644
index 00000000..b3ffc0f4
--- /dev/null
+++ b/ryven/gui/code_editor/codes_storage.py
@@ -0,0 +1,89 @@
+# statically stores source codes of nodes and their widgets
+from dataclasses import dataclass
+from typing import Type, Optional
+import inspect
+
+from ryvencore import Node
+from ryven.main.config import instance
+
+
+def register_node_type(n: Type[Node]):
+    """
+    Inspects and stores source code of a node type.
+    """
+
+    has_gui = hasattr(n, 'GUI')  # check if node type has custom gui
+    has_mw = has_gui and n.GUI.main_widget_class is not None
+
+    src = inspect.getsource(n)
+    mw_src = inspect.getsource(n.GUI.main_widget_class) if has_mw else None
+    inp_src = {
+        name: inspect.getsource(cls)
+        for name, cls in n.GUI.input_widget_classes.items()
+    } if has_gui else None
+
+    class_codes[n] = NodeTypeCodes(
+        node_cls=src,
+        main_widget_cls=mw_src,
+        custom_input_widget_clss=inp_src,
+    )
+
+    if instance.src_code_edits_enabled:
+        # store full module source code
+        mod_codes[n] = inspect.getsource(inspect.getmodule(n))
+        if has_mw:
+            mod_codes[n.GUI.main_widget_class] = \
+                inspect.getsource(inspect.getmodule(n.GUI.main_widget_class))
+            for inp_cls in n.GUI.input_widget_classes.values():
+                mod_codes[inp_cls] = inspect.getsource(inspect.getmodule(inp_cls))
+
+
+@dataclass
+class NodeTypeCodes:
+    node_cls: str
+    main_widget_cls: Optional[str]
+    custom_input_widget_clss: {str: str}
+
+
+class Inspectable:
+    """
+    Represents an object whose source code can be inspected.
+    This is either a node or some node widget.
+    Used by the code editor to store polymorphic references to
+    objects which can be inspected.
+    """
+    def __init__(self, node, obj, code):
+        self.node = node
+        self.obj = obj
+        self.code = code
+
+
+class NodeInspectable(Inspectable):
+    def __init__(self, node, code):
+        super().__init__(node, node, code)
+
+
+class MainWidgetInspectable(Inspectable):
+    pass
+
+
+class CustomInputWidgetInspectable(Inspectable):
+    pass
+
+
+class_codes: {Type[Node]: {}} = {}
+# {
+#     Type[Node]: NodeTypeCodeInfo
+# }
+
+
+#
+# below fields are only used when src code edits are enabled
+#
+
+
+# maps node- or widget classes to their full module source code
+mod_codes: {Type: str} = {}
+
+# maps node- or widget objects to their modified source code
+modif_codes: {object: str} = {}
diff --git a/ryven/gui/dialogs.py b/ryven/gui/dialogs.py
index f46a7d75..a86789d9 100644
--- a/ryven/gui/dialogs.py
+++ b/ryven/gui/dialogs.py
@@ -22,26 +22,26 @@ def get_text(self):
         return self.text
 
 
-class ChooseScriptDialog(QDialog):
-    def __init__(self, window_title, scripts, parent=None):
+class ChooseFlowDialog(QDialog):
+    def __init__(self, window_title, flows, parent=None):
         super().__init__(parent)
 
-        self.script = None
-        self.button_scripts = {}
+        self.flow = None
+        self.button_flows = {}
 
         self.setLayout(QVBoxLayout())
-        for s in scripts:
+        for s in flows:
             b = QPushButton(s.title)
             b.pressed.connect(self.button_pressed)
-            self.button_scripts[b] = s
+            self.button_flows[b] = s
             self.layout().addWidget(b)
 
         self.setWindowTitle(window_title)
 
     def button_pressed(self):
-        self.script = self.button_scripts[self.sender()]
+        self.flow = self.button_flows[self.sender()]
         self.accept()
 
-    def get_script(self):
+    def get_flow(self):
         self.exec_()
-        return self.script
+        return self.flow
diff --git a/ryven/gui/script_UI.py b/ryven/gui/flow_ui.py
similarity index 69%
rename from ryven/gui/script_UI.py
rename to ryven/gui/flow_ui.py
index 10566923..24cfee62 100644
--- a/ryven/gui/script_UI.py
+++ b/ryven/gui/flow_ui.py
@@ -1,15 +1,15 @@
-"""A UI for scripts. Will be displayed in the tab widget in MainWindow."""
+"""A UI for flows. Will be displayed in the tab widget in MainWindow."""
 
 from qtpy.QtWidgets import QWidget, QHBoxLayout, QComboBox
 
-import ryvencore_qt.src.conv_gui as GUI
+import ryvencore_qt.src.widgets as GUI
 from ryvencore.RC import FlowAlg
 
 from ryven.gui.code_editor.CodePreviewWidget import CodePreviewWidget
-from ryven.gui.uic.ui_script import Ui_script_widget
+from ryven.gui.uic.ui_flow import Ui_flow_widget
 
 
-class ScriptUI(QWidget):
+class FlowUI(QWidget):
 
     flow_alg_mode_display_titles = {
         FlowAlg.DATA: 'data-flow',
@@ -17,18 +17,17 @@ class ScriptUI(QWidget):
         FlowAlg.EXEC: 'exec-flow',
     }
 
-    def __init__(self, main_window, script, flow_view):
+    def __init__(self, main_window, flow, flow_view):
         super().__init__(main_window)
 
-        self.script = script
+        self.flow = flow
         self.flow_view = flow_view
         
         # UI
-        self.ui = Ui_script_widget()
+        self.ui = Ui_flow_widget()
         self.ui.setupUi(self)
 
-        self.script.flow.algorithm_mode_changed.connect(self.flow_alg_mode_changed)
-        # self.script.flow_view.viewport_update_mode_changed.connect(self.flow_vp_update_mode_changed)
+        self.flow.algorithm_mode_changed.sub(self.flow_alg_mode_changed)
 
         self.flow_alg_mode_dropdown = QComboBox()
         for mode, title in self.flow_alg_mode_display_titles.items():
@@ -37,11 +36,10 @@ def __init__(self, main_window, script, flow_view):
         self.flow_alg_mode_dropdown.currentTextChanged.connect(self.flow_algorithm_mode_toggled)
 
         # catch up on flow modes
-        self.flow_alg_mode_changed(self.script.flow.algorithm_mode())
-        # self.flow_vp_update_mode_changed(self.script.flow_view.viewport_update_mode())
+        self.flow_alg_mode_changed(self.flow.algorithm_mode())
 
         # variables list widget
-        self.vars_list_widget = GUI.VarsList(self.script.vars_manager)
+        self.vars_list_widget = GUI.VarsList(self.flow.session.addons.get('Variables'), self.flow) # TODO: how are vars now managed?
         self.ui.variables_group_box.layout().addWidget(self.vars_list_widget)
         self.ui.settings_vars_splitter.setSizes([40, 700])
 
@@ -55,11 +53,15 @@ def __init__(self, main_window, script, flow_view):
         # logs
         self.ui.logs_scrollArea.setWidget(self.create_loggers_widget())
         self.ui.splitter.setSizes([700, 0])
-        self.script.logs_manager.new_logger_created.connect(self.add_logger_widget)
+
+        # TODO: need to connect to logging event but seems it isn't implemented yet
+        #self.flow.session.addons.get('Logging').logs_manager.new_logger_created.connect(self.add_logger_widget)
 
         # catch up on logs which might have been loaded from a project already
-        for logger in self.script.logs_manager.loggers:
+        logging = self.flow.session.addons.get('Logging')
+        for logger in logging.loggers:
             self.add_logger_widget(logger)
+        logging.log_created.sub(self.add_logger_widget)
 
 
     def create_loggers_widget(self):
@@ -81,7 +83,7 @@ def flow_alg_mode_changed(self, mode: str):
     
     def flow_algorithm_mode_toggled(self):
 
-        self.script.flow.set_algorithm_mode(
+        self.flow.set_algorithm_mode(
             FlowAlg.str(
                 list(self.flow_alg_mode_display_titles.keys())
                 [self.flow_alg_mode_dropdown.currentIndex()]
diff --git a/ryven/gui/main_window.py b/ryven/gui/main_window.py
index ad56c801..7376b1c3 100644
--- a/ryven/gui/main_window.py
+++ b/ryven/gui/main_window.py
@@ -1,85 +1,130 @@
 import sys
-from os.path import join, dirname
+import os
+import os.path
 
 from qtpy.QtGui import QIcon, QKeySequence
-from qtpy.QtWidgets import QMainWindow, QFileDialog, QDialog, QShortcut, QAction, QActionGroup, QMenu, QMessageBox
+from qtpy.QtWidgets import QMainWindow, QFileDialog, QShortcut, QAction, QActionGroup, QMenu, QMessageBox
+from ryvencore_qt import NodeGUI
 
-from ryven.gui.main_console import *
-from ryven.gui.script_UI import ScriptUI
-from ryven.gui.styling.window_theme import WindowTheme
-from ryven.main.nodes_package import NodesPackage
+from ryven.gui.main_console import MainConsole
+from ryven.gui.flow_ui import FlowUI
+from ryven.main.config import Config
+from ryven.main.packages.nodes_package import NodesPackage
 from ryven.gui.uic.ui_main_window import Ui_MainWindow
-from ryven.main.utils import import_nodes_package, abs_path_from_package_dir, abs_path_from_ryven_dir
-from ryven.main.nodes.NodeBase import NodeBase
-from ryven.gui.dialogs import GetTextDialog, ChooseScriptDialog
+from ryven.main.utils import \
+    abs_path_from_package_dir, \
+    abs_path_from_ryven_dir, \
+    ryven_version
+from ryven import import_nodes_package
+from ryven.gui.dialogs import GetTextDialog, ChooseFlowDialog
 
 # ryvencore_qt
 import ryvencore_qt as rc
-import ryvencore_qt.src.conv_gui as rc_GUI
+import ryvencore_qt.src.widgets as rc_GUI
+
+from ryvencore import InfoMsgs
 
 
 class MainWindow(QMainWindow):
 
-    def __init__(self, config: dict, window_title, window_theme: WindowTheme, flow_theme, parent=None):
+    def __init__(
+            self,
+
+            config: Config,
+            requested_packages: set = (),
+            required_packages: set = None,  # only valid when project_content is provided
+            project_content: dict = None,
+
+            parent=None,
+    ):
         super().__init__(parent)
 
-        self.session = None
-        self.theme = window_theme
+        self.config = config
+        self.session_gui, self.core_session = None, None
+        self.theme = config.window_theme
         self.node_packages = {}  # {Node: str}
-        self.script_UIs = {}
+        self.flow_UIs = {}
 
-        # SESSION
-        self.session = rc.Session()
-        # self.session.info_messenger().enable(True)
+        #
+        # Init Session GUI
+        #
 
-        self.session.flow_view_created.connect(self.script_created)
-        self.session.script_renamed.connect(self.script_renamed)
-        self.session.script_deleted.connect(self.script_deleted)
+        self.session_gui = rc.SessionGUI(self)
+        self.core_session = self.session_gui.core_session
+        if self.config.verbose:
+            self.core_session._info_messenger().enable(traceback=True)
+        else:
+            self.core_session._info_messenger().disable()
 
-        # LOAD DESIGN AND FLOW THEME
-        self.session.design.load_from_config(abs_path_from_package_dir('gui/styling/design_config.json'))
+        self.session_gui.flow_view_created.connect(self.flow_created)
+        self.session_gui.flow_renamed.connect(self.flow_renamed)
+        self.session_gui.flow_deleted.connect(self.flow_deleted)
 
-        self.session.design.set_flow_theme(name=flow_theme)
+        # unused; default flow theme etc. are defined by Config
+        # self.session_gui.design.load_from_config(abs_path_from_package_dir('gui/styling/design_config.json'))
+
+        self.session_gui.design.set_flow_theme(name=self.config.flow_theme)
+        self.session_gui.design.set_performance_mode(self.config.performance_mode)
+        self.session_gui.design.set_animations_enabled(self.config.animations)
+
+        #
+        # Assemble Window UI
+        #
 
-        # UI
         self.setup_ui()
 
         self.flow_view_theme_actions = []
         self.setup_menu_actions()
 
-        self.setWindowTitle(window_title)
+        self.setWindowTitle(self.config.window_title)
         self.setWindowIcon(QIcon(abs_path_from_package_dir('resources/pics/Ryven_icon.png')))
-        self.ui.scripts_tab_widget.removeTab(0)  # remove placeholder tab
+        self.ui.flows_tab_widget.removeTab(0)  # remove placeholder tab
+
+        #
+        # Configure window-wide Shortcuts
+        #
 
-        # SHORTCUTS
         save_shortcut = QShortcut(QKeySequence.Save, self)
         save_shortcut.activated.connect(self.on_save_project_triggered)
         import_nodes_shortcut = QShortcut(QKeySequence('Ctrl+i'), self)
         import_nodes_shortcut.activated.connect(self.on_import_nodes_triggered)
 
-        # PROJECT SETUP
-        if 'info_msgs' in sys.argv:
-            rc.InfoMsgs.enable()
+        #
+        # Setup Main Console
+        #
 
-        #   SETUP MAIN CONSOLE
-        MainConsole.instance.session = self.session
+        MainConsole.instance.session = self.session_gui
         MainConsole.instance.reset_interpreter()
-        NodeBase.main_console = MainConsole.instance
 
-        #   REGISTER BUILT-IN NODES
-        self.import_nodes(path=abs_path_from_package_dir('main/nodes/built_in/'))
-
-        #   LOAD PROJECT
-        if config['action'] == 'create project':
-            self.session.create_script(title='hello world')
-        elif config['action'] == 'open project':
-            print('importing packages...')
-            self.import_packages(config['required packages'])
+        # very dirty hack to access nodes from the console
+        def console_ref_monkeypatch(self):
+            MainConsole.instance.add_obj_context(self.node)
+        NodeGUI.console_ref_monkeypatch = console_ref_monkeypatch
+        old_ac_init = NodeGUI._init_default_actions
+        NodeGUI._init_default_actions = lambda self: {
+            **old_ac_init(self),
+            'console ref': {'method': self.console_ref_monkeypatch}
+        }
+
+        #
+        # Setup ryvencore Session and load project
+        #
+
+        self.import_nodes(path=abs_path_from_package_dir('main/packages/built_in/'))
+
+        # Requested packages take precedence over other packages
+        print('importing requested packages...')
+        self.import_packages(requested_packages)
+        if project_content is not None:
+            print('importing required packages...')
+            self.import_packages(required_packages)
             print('loading project...')
-            self.session.load(config['content'])
-            print('finished')
+            self.core_session.load(project_content)
+            print('done')
+        else:
+            self.core_session.create_flow(title='hello world')
 
-        self.resize(1500, 800)
+        self.resize(1500, 800)  # FIXME: this renders the --geometry argument useless, no?
         # self.showMaximized()
 
     def print_info(self):
@@ -111,10 +156,10 @@ def setup_ui(self):
         # self.ui.left_vertical_splitter.setSizes([350, 350])
         self.ui.main_vertical_splitter.setSizes([700, 0])
 
-        self.scripts_list_widget = rc_GUI.ScriptsList(self.session)
-        self.ui.scripts_groupBox.layout().addWidget(self.scripts_list_widget)
+        self.flows_list_widget = rc_GUI.FlowsList(self.session_gui)
+        self.ui.flows_groupBox.layout().addWidget(self.flows_list_widget)
 
-        self.nodes_list_widget = rc_GUI.NodeListWidget(self.session)
+        self.nodes_list_widget = rc_GUI.NodeListWidget(self.session_gui)
         self.ui.nodes_groupBox.layout().addWidget(self.nodes_list_widget)
 
         self.ui.main_horizontal_splitter.setSizes([120, 800-120])
@@ -123,7 +168,7 @@ def setup_menu_actions(self):
 
         # flow designs
         light_themes_menu = QMenu('light')
-        for d in self.session.design.flow_themes:
+        for d in self.session_gui.design.flow_themes:
             design_action = QAction(d.name, self)
             if d.type_ == 'dark':
                 self.ui.menuFlow_Design_Style.addAction(design_action)
@@ -138,9 +183,10 @@ def setup_menu_actions(self):
         self.ui.actionImport_Nodes.triggered.connect(self.on_import_nodes_triggered)
         self.ui.actionImport_Example_Nodes.triggered.connect(self.on_import_example_nodes_triggered)
         self.ui.actionSave_Project.triggered.connect(self.on_save_project_triggered)
-        self.ui.actionNew_Script.triggered.connect(self.on_new_script_triggered)
-        self.ui.actionRename_Script.triggered.connect(self.on_rename_script_triggered)
-        self.ui.actionDelete_Script.triggered.connect(self.on_delete_script_triggered)
+        # TODO: rename occurences of "Script" to "Flow" in the UI
+        self.ui.actionNew_Flow.triggered.connect(self.on_new_flow_triggered)
+        self.ui.actionRename_Flow.triggered.connect(self.on_rename_flow_triggered)
+        self.ui.actionDelete_Flow.triggered.connect(self.on_delete_flow_triggered)
         self.ui.actionEnableInfoMessages.triggered.connect(self.on_enable_info_msgs_triggered)
         self.ui.actionDisableInfoMessages.triggered.connect(self.on_disable_info_msgs_triggered)
         self.ui.actionSave_Pic_Viewport.triggered.connect(self.on_save_scene_pic_viewport_triggered)
@@ -157,8 +203,8 @@ def setup_menu_actions(self):
         perf_mode_ag.addAction(self.ac_perf_mode_fast)
         perf_mode_ag.addAction(self.ac_perf_mode_pretty)
 
-        self.ac_perf_mode_fast.setChecked(self.session.design.performance_mode == 'fast')
-        self.ac_perf_mode_pretty.setChecked(self.session.design.performance_mode == 'pretty')
+        self.ac_perf_mode_fast.setChecked(self.session_gui.design.performance_mode == 'fast')
+        self.ac_perf_mode_pretty.setChecked(self.session_gui.design.performance_mode == 'pretty')
 
         perf_mode_ag.triggered.connect(self.on_performance_mode_changed)
 
@@ -179,8 +225,8 @@ def setup_menu_actions(self):
         anims_ag.addAction(self.ac_anims_active)
         anims_ag.addAction(self.ac_anims_inactive)
 
-        self.ac_anims_active.setChecked(self.session.design.animations_enabled)
-        self.ac_anims_inactive.setChecked(not self.session.design.animations_enabled)
+        self.ac_anims_active.setChecked(self.session_gui.design.animations_enabled)
+        self.ac_anims_inactive.setChecked(not self.session_gui.design.animations_enabled)
 
         anims_ag.triggered.connect(self.on_animation_enabling_changed)
 
@@ -197,127 +243,126 @@ def load_stylesheet(self, ss):
             ss_content = f.read()
             f.close()
         finally:
-            self.session.set_stylesheet(ss_content)
+            self.session_gui.set_stylesheet(ss_content)
             self.setStyleSheet(ss_content)
 
     # SLOTS
 
     def on_import_nodes_triggered(self):
-        file_path = QFileDialog.getOpenFileName(self, 'select nodes file', abs_path_from_ryven_dir('nodes'), '(*.py)', )[0]
+        file_path = QFileDialog.getOpenFileName(self, 'select nodes file', abs_path_from_ryven_dir('nodes'), 'Python File (*.py)')[0]
         if file_path != '':
-            self.import_nodes(path=dirname(file_path))
+            self.import_nodes(path=os.path.dirname(file_path))
 
     def on_import_example_nodes_triggered(self):
-        file_path = QFileDialog.getOpenFileName(self, 'select nodes file', abs_path_from_package_dir('example_nodes'), '(*.py)', )[0]
+        file_path = QFileDialog.getOpenFileName(self, 'select nodes file', abs_path_from_package_dir('example_nodes'), 'Python File (*.py)')[0]
         if file_path != '':
-            self.import_nodes(path=dirname(file_path))
+            self.import_nodes(path=os.path.dirname(file_path))
 
     def on_performance_mode_changed(self, action):
         if action == self.ac_perf_mode_fast:
-            self.session.design.set_performance_mode('fast')
+            self.session_gui.design.set_performance_mode('fast')
         else:
-            self.session.design.set_performance_mode('pretty')
+            self.session_gui.design.set_performance_mode('pretty')
 
     def on_animation_enabling_changed(self, action):
         if action == self.ac_anims_active:
-            self.session.design.animations_enabled = True
+            self.session_gui.design.animations_enabled = True
         else:
-            self.session.design.animations_enabled = False
+            self.session_gui.design.animations_enabled = False
 
     def on_design_action_triggered(self):
         index = self.flow_view_theme_actions.index(self.sender())
-        self.session.design.set_flow_theme(self.session.design.flow_themes[index])
+        self.session_gui.design.set_flow_theme(self.session_gui.design.flow_themes[index])
 
     def on_enable_info_msgs_triggered(self):
-        rc.InfoMsgs.enable()
+        InfoMsgs.enable()
 
     def on_disable_info_msgs_triggered(self):
-        rc.InfoMsgs.disable()
+        InfoMsgs.disable()
 
     def on_save_scene_pic_viewport_triggered(self):
         """Saves a picture of the currently visible viewport."""
-        if len(self.session.scripts) == 0:
+        if len(self.core_session.flows) == 0:
             return
 
         file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0]
-        script = self.ui.scripts_tab_widget.currentWidget().script
-        view = self.session.flow_views[script]
+        flow = self.ui.flows_tab_widget.currentWidget().flow
+        view = self.session_gui.flow_views[flow]
         img = view.get_viewport_img()
         img.save(file_path)
 
     def on_save_scene_pic_whole_triggered(self):
         """Saves a picture of the whole currently visible scene."""
-        if len(self.session.scripts) == 0:
+        if len(self.session_gui.flows) == 0:
             return
 
         file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0]
-        script = self.ui.scripts_tab_widget.currentWidget().script
-        view = self.session.flow_views[script]
+        flow = self.ui.flows_tab_widget.currentWidget().flow
+        view = self.session_gui.flow_views[flow]
         img = view.get_whole_scene_img()
         img.save(file_path)
 
     def on_save_project_triggered(self):
         file_name = QFileDialog.getSaveFileName(self, 'select location and give file name',
-                                                abs_path_from_ryven_dir('saves'), '(*.json)')[0]
+                                                abs_path_from_ryven_dir('saves'), 'JSON(*.json)')[0]
+        if not file_name.endswith('.json'):
+            file_name += '.json'
+
         if file_name != '':
             self.save_project(file_name)
 
-    def on_new_script_triggered(self):
-        new_script_title = GetTextDialog('choose unique title', '', 'new script title', self).get_text()
+    def on_new_flow_triggered(self):
+        new_flow_title = GetTextDialog('choose unique title', '', 'new flow title', self).get_text()
 
-        if new_script_title not in (s.title for s in self.session.scripts):
-            self.session.create_script(new_script_title)
+        if self.core_session.flow_title_valid(new_flow_title):
+            self.core_session.create_flow(new_flow_title)
         else:
-            script = [s for s in self.session.scripts if s.title == new_script_title][0]
-            self.focus_on_script(script)
+            flow = [f for f in self.core_session.flows if f.title == new_flow_title][0]
+            self.focus_on_flow(flow)
 
-    def on_rename_script_triggered(self):
-        script = ChooseScriptDialog('choose script', self.session.scripts, self).get_script()
-        new_title = GetTextDialog('new title', script.title, 'new script title', self).get_text()
+    def on_rename_flow_triggered(self):
+        flow = ChooseFlowDialog('choose flow', self.core_session.flows, self).get_flow()
+        new_title = GetTextDialog('new title', flow.title, 'new flow title', self).get_text()
 
-        all_other_script_titles = [
-            s.title for s in self.session.scripts if s != script
-        ]
+        if self.core_session.flow_title_valid(new_title):
+            self.core_session.rename_flow(flow, new_title)
 
-        if new_title not in all_other_script_titles:
-            self.session.rename_script(script, new_title)
+    def on_delete_flow_triggered(self):
+        flow = ChooseFlowDialog('choose flow', self.core_session.flows, self).get_flow()
 
-    def on_delete_script_triggered(self):
-        script = ChooseScriptDialog('choose script', self.session.scripts, self).get_script()
-
-        msg_box = QMessageBox(QMessageBox.Warning, 'sure about deleting script?',
-                              'You are about to delete a script. This cannot be undone, all content will be gone. '
+        msg_box = QMessageBox(QMessageBox.Warning, 'sure about deleting flow?',
+                              'You are about to delete a flow. This cannot be undone, all content will be gone. '
                               'Do you want to continue?', QMessageBox.Cancel | QMessageBox.Yes, self)
         msg_box.setDefaultButton(QMessageBox.Cancel)
         ret = msg_box.exec_()
         if ret != QMessageBox.Yes:
             return
 
-        self.session.delete_script(script)
+        self.core_session.delete_flow(flow)
 
     # SESSION
 
-    def script_created(self, script, flow_view):
-        script_widget = ScriptUI(self, script, flow_view)
-        self.script_UIs[script] = script_widget
-        self.ui.scripts_tab_widget.addTab(script_widget, script.title)
-        self.focus_on_script(script)
+    def flow_created(self, flow, flow_view):
+        flow_widget = FlowUI(self, flow, flow_view)
+        self.flow_UIs[flow] = flow_widget
+        self.ui.flows_tab_widget.addTab(flow_widget, flow.title)
+        self.focus_on_flow(flow)
 
-    def script_renamed(self, script):
-        self.ui.scripts_tab_widget.setTabText(
-            self.session.scripts.index(script),
-            script.title
+    def flow_renamed(self, flow):
+        self.ui.flows_tab_widget.setTabText(
+            self.session_gui.core_session.flows.index(flow),
+            flow.title
         )
 
-    def script_deleted(self, script):
-        self.ui.scripts_tab_widget.removeTab(self.ui.scripts_tab_widget.indexOf(self.script_UIs[script]))
-        del self.script_UIs[script]
+    def flow_deleted(self, flow):
+        self.ui.flows_tab_widget.removeTab(self.ui.flows_tab_widget.indexOf(self.flow_UIs[flow]))
+        del self.flow_UIs[flow]
 
-    def get_current_script(self):
-        return self.session.scripts[self.ui.scripts_tab_widget.currentIndex()]
+    def get_current_flow(self):
+        return self.core_session.flows[self.ui.flows_tab_widget.currentIndex()]
 
-    def focus_on_script(self, script):
-        self.ui.scripts_tab_widget.setCurrentWidget(self.script_UIs[script])
+    def focus_on_flow(self, flow):
+        self.ui.flows_tab_widget.setCurrentWidget(self.flow_UIs[flow])
 
     def import_packages(self, packages_list: [NodesPackage]):
         for p in packages_list:
@@ -330,19 +375,26 @@ def import_nodes(self, package: NodesPackage = None, path: str = None):
         else:
             p = NodesPackage(path)
 
+        if p in self.node_packages.values():
+            # never import package twice!
+            # different packages with same name are forbidden
+            print('package with this name already exists')
+            return
+
         try:
-            nodes = import_nodes_package(p)
+            nodes, data_types = import_nodes_package(p)
         except ModuleNotFoundError as e:
             msg_box = QMessageBox(QMessageBox.Warning, 'Missing Python module', str(e), QMessageBox.Ok, self)
             msg_box.exec_()
-            sys.exit()
+            sys.exit(e)
 
-        self.session.register_nodes(nodes)
+        self.core_session.register_data_types(data_types)
+        self.core_session.register_node_types(nodes)
 
         for n in nodes:
             self.node_packages[n] = p
 
-        self.nodes_list_widget.update_list(self.session.nodes)
+        self.nodes_list_widget.update_list(self.core_session.nodes)
 
     def save_project(self, file_name):
         import json
@@ -353,16 +405,15 @@ def save_project(self, file_name):
                 os.remove(file_name)
             file = open(file_name, 'w')
         except FileNotFoundError:
-            rc.InfoMsgs.write('couldn\'t open file')
+            InfoMsgs.write('couldn\'t open file')
             return
 
-        # TODO: parametrize ryven version from package info
-        general_project_info_dict = {'type': 'Ryven project file', 'ryven version': 'v3.1'}
+        general_project_info_dict = {'type': 'Ryven project file', 'ryven version': str(ryven_version())}
 
-        scripts_data = self.session.serialize()
+        flows_data = self.core_session.serialize()
 
         required_packages = set()
-        for node in self.session.all_node_objects():
+        for node in self.core_session.all_node_objects():
             if node.__class__ not in self.node_packages.keys() or \
                     self.node_packages[node.__class__] is None or \
                     self.node_packages[node.__class__].name == 'built_in':
@@ -373,10 +424,10 @@ def save_project(self, file_name):
 
         whole_project_dict = {'general info': general_project_info_dict,
                               'required packages': [p.config_data() for p in required_packages],
-                              **scripts_data}
+                              **flows_data}
 
         data = json.dumps(whole_project_dict, indent=4)
-        rc.InfoMsgs.write(data)
+        InfoMsgs.write(data)
 
         file.write(data)
         file.close()
diff --git a/ryven/gui/startup_dialog/SelectPackages_Dialog.py b/ryven/gui/startup_dialog/SelectPackages_Dialog.py
deleted file mode 100644
index 7b21d712..00000000
--- a/ryven/gui/startup_dialog/SelectPackages_Dialog.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import sys
-
-from qtpy.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QWidget, QLabel, \
-    QListWidget, QListWidgetItem
-import os
-from os.path import basename, abspath, dirname, normpath, join, splitext
-
-from ryven.main.nodes_package import NodesPackage
-from ryven.main.utils import abs_path_from_package_dir, abs_path_from_ryven_dir
-
-
-class SelectPackages_Dialog(QDialog):
-    """
-    Now only used to state new paths to required packages that are not valid anymore.
-    The 'auto import' feature is therefore probably useless by now.
-    """
-
-    def __init__(self, parent, required_packages: [str]):
-        super(SelectPackages_Dialog, self).__init__(parent)
-
-        self.file_paths = []
-        self.required_packages: [str] = required_packages
-        self.packages = []
-
-        self.setLayout(QVBoxLayout())
-
-        self.layout().addWidget(QLabel('You need to select the locations of the following required node packages'))
-
-        # package lists
-        required_packages_list_widget = QListWidget()
-        for p_name in required_packages:
-            required_packages_list_widget.addItem(QListWidgetItem(p_name))
-
-        selected_items_widget = QWidget()
-        selected_items_widget.setLayout(QVBoxLayout())
-        self.selected_packages_list_widget = QListWidget()
-        selected_items_widget.layout().addWidget(self.selected_packages_list_widget)
-
-        auto_import_button = QPushButton('auto import')
-        auto_import_button.setFocus()
-        auto_import_button.clicked.connect(self.auto_import_button_clicked)
-        selected_items_widget.layout().addWidget(auto_import_button)
-
-        add_package_button = QPushButton('add')
-        add_package_button.clicked.connect(self.add_package_button_clicked)
-        selected_items_widget.layout().addWidget(add_package_button)
-
-        clear_package_list_button = QPushButton('clear')
-        clear_package_list_button.clicked.connect(self.clear_selected_packages_list)
-        selected_items_widget.layout().addWidget(clear_package_list_button)
-
-        finished_button = QPushButton('OK')
-        finished_button.clicked.connect(self.finished_button_clicked)
-        selected_items_widget.layout().addWidget(finished_button)
-
-        packages_lists_widget = QWidget()
-        packages_lists_widget.setLayout(QHBoxLayout())
-        packages_lists_widget.layout().addWidget(required_packages_list_widget)
-        packages_lists_widget.layout().addWidget(selected_items_widget)
-
-        self.layout().addWidget(packages_lists_widget)
-
-        self.setWindowTitle('select required packages')
-
-    def auto_import_button_clicked(self):
-
-        # search in user packages
-        self.auto_import(packages_dir=abs_path_from_ryven_dir('packages'))
-        if self.all_required_packages_selected():
-            self.finished()
-
-        # search in example packages
-        self.auto_import(packages_dir=abs_path_from_package_dir('example_nodes'))
-        if self.all_required_packages_selected():
-            self.finished()
-
-    def auto_import(self, packages_dir: str):
-
-        folders_list = [basename(x[0]) for x in os.walk(packages_dir) if
-                        basename(x[0]) in self.required_packages]
-
-        required_packages = self.required_packages.copy()
-
-        for pkg_name in required_packages:
-
-            p_dir = join(packages_dir, pkg_name)
-            p_file = normpath(join(p_dir, 'nodes.py'))
-
-            if pkg_name in folders_list and \
-                    'nodes.py' in os.listdir(p_dir) and \
-                    p_file not in self.file_paths:
-
-                self.file_paths.append(p_file)
-                self.packages.append(NodesPackage(dirname(p_file)))
-
-        self.rebuild_selected_packages_list_widget()
-
-        self.clean_packages_list()
-
-    def add_package_button_clicked(self):
-
-        file_names = QFileDialog.getOpenFileNames(
-            self,
-            'select package file (nodes.py)',
-            abs_path_from_ryven_dir('packages'),
-            '(*.py)'
-        )[0]
-
-        for file_name in file_names:
-            try:
-                # simply try to open the file to make sure it's valid
-                f = open(file_name)
-                f.close()
-
-                self.file_paths.append(file_name)
-                self.packages.append(NodesPackage(dirname(file_name)))
-
-            except FileNotFoundError:
-                pass
-
-        self.rebuild_selected_packages_list_widget()
-
-    def rebuild_selected_packages_list_widget(self):
-        # remove all items
-        self.selected_packages_list_widget.clear()
-
-        for f in self.file_paths:
-            file_item = QListWidgetItem()
-            file_item.setText(f)
-            self.selected_packages_list_widget.addItem(file_item)
-
-    def clear_selected_packages_list(self):
-        self.file_paths.clear()
-        self.packages.clear()
-
-        self.rebuild_selected_packages_list_widget()
-
-    def finished_button_clicked(self):
-        self.clean_packages_list()
-        if self.all_required_packages_selected():
-            self.finished()
-
-    def clean_packages_list(self):
-        """remove duplicates from self.file_paths"""
-
-        files_dict = {}
-
-        for p in self.file_paths:
-            package_name = basename(dirname(p))
-            files_dict[package_name] = p
-
-        self.file_paths = list(files_dict.values())
-
-        self.rebuild_selected_packages_list_widget()
-
-    def all_required_packages_selected(self):
-
-        selected_packages = [basename(normpath(np.directory)) for np in self.packages]
-
-        # search for missing packages
-        for p in self.required_packages:
-            if p not in selected_packages:
-                return False
-
-        return True
-
-    def finished(self):
-        self.accept()
-
-    def closeEvent(self, arg__1) -> None:
-        sys.exit()
diff --git a/ryven/gui/startup_dialog/StartupDialog.py b/ryven/gui/startup_dialog/StartupDialog.py
index 45372817..ded851b9 100644
--- a/ryven/gui/startup_dialog/StartupDialog.py
+++ b/ryven/gui/startup_dialog/StartupDialog.py
@@ -1,152 +1,702 @@
-from qtpy.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QFileDialog, QRadioButton
-from qtpy.QtGui import QIcon
-
-from ryven.main.nodes_package import NodesPackage
-from ryven.main.utils import abs_path_from_package_dir, abs_path_from_ryven_dir
+import os
+import pathlib
+from typing import Optional
+
+from qtpy.QtWidgets import (
+    QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QFileDialog,
+    QRadioButton, QButtonGroup, QLabel, QFormLayout, QComboBox, QDialogButtonBox,
+    QCheckBox, QListWidget, QListWidgetItem, QMessageBox,
+    QStyleOptionFrame, QStyle, QLineEdit)
+from qtpy.QtCore import Qt, QSize
+from qtpy.QtGui import QIcon, QPainter
+
+from ryven.main.args_parser import unparse_sys_args
+from ryven.main.config import Config
+from ryven.main.utils import (
+    abs_path_from_package_dir, abs_path_from_ryven_dir, ryven_dir_path)
+from ryven.main.packages.nodes_package import process_nodes_packages
 from ryven.gui.styling.window_theme import apply_stylesheet
-from ryven.gui.startup_dialog.SelectPackages_Dialog import SelectPackages_Dialog
 
 
-class StartupDialog(QDialog):
+LBL_CREATE_PROJECT = '<create new project>'
+LBL_DEFAULT_FLOW_THEME = '<default>'
+
+
+class ElideLabel(QLabel):
+    """A QLabel with ellipsis, if the text is too long to be fully displayed.
+
+    See:
+        https://stackoverflow.com/questions/68092087/one-line-elideable-qlabel#answer-68092991
+
+    Copyright (C) 2021  https://github.com/MaurizioB/
     """
-    The welcome dialog. The user can choose between creating a new project and loading a saved project.
-    When a project is loaded, it scans for validity of all the required packages for the project, and in case
-    some paths are invalid, the SelectPackages_Dialog is opened to get the paths to those missing package files.
+    _elideMode = Qt.ElideMiddle
+
+    def setText(self, label, *args, **kwargs):
+        """Sets text and tooltip."""
+        s = str(label)
+        super().setText(s, *args, **kwargs)
+        self.setToolTip(s)
+
+    def elideMode(self):
+        return self._elideMode
+
+    def setElideMode(self, mode):
+        if self._elideMode != mode and mode != Qt.ElideNone:
+            self._elideMode = mode
+            self.updateGeometry()
+
+    def minimumSizeHint(self):
+        return self.sizeHint()
+
+    def sizeHint(self):
+        hint = self.fontMetrics().boundingRect(self.text()).size()
+        l, t, r, b = self.getContentsMargins()
+        margin = self.margin() * 2
+        return QSize(
+            min(100, hint.width()) + l + r + margin,
+            min(self.fontMetrics().height(), hint.height()) + t + b + margin
+        )
+
+    def paintEvent(self, event):
+        qp = QPainter(self)
+        opt = QStyleOptionFrame()
+        self.initStyleOption(opt)
+        self.style().drawControl(
+            QStyle.CE_ShapedFrame, opt, qp, self)
+        l, t, r, b = self.getContentsMargins()
+        margin = self.margin()
+        try:
+            # since Qt >= 5.11
+            m = self.fontMetrics().horizontalAdvance('x') / 2 - margin
+        except:
+            m = self.fontMetrics().width('x') / 2 - margin
+        r = self.contentsRect().adjusted(
+            margin + m,  margin, -(margin + m), -margin)
+        qp.drawText(r, self.alignment(),
+            self.fontMetrics().elidedText(
+                self.text(), self.elideMode(), r.width()))
+
+
+class ShowCommandDialog(QDialog):
+    def __init__(self, command, config, parent=None):
+        super().__init__(parent)
+
+        layout = QVBoxLayout()
+        command_lineedit = QLineEdit(command)
+        layout.addWidget(command_lineedit)
+        self.config_text_edit = QTextEdit()
+        self.config_text_edit.setText(config)
+        layout.addWidget(self.config_text_edit)
+
+        buttons_layout = QHBoxLayout()
+        save_config_button = QPushButton('save config')
+        save_config_button.clicked.connect(self.on_save_config_clicked)
+        buttons_layout.addWidget(save_config_button)
+        close_button = QPushButton('close')
+        close_button.clicked.connect(self.accept)
+        buttons_layout.addWidget(close_button)
+        layout.addLayout(buttons_layout)
+
+        self.setLayout(layout)
+
+    def on_save_config_clicked(self):
+        config = self.config_text_edit.toPlainText()
+
+        file = QFileDialog.getSaveFileName(
+            self,
+            'select config file',
+            ryven_dir_path(),
+            'ryven config files (*.cfg)',
+        )[0]
+
+        if file != '':
+            p = pathlib.Path(file)
+            with open(p, 'w') as f:
+                f.write(config)
+
+
+class StartupDialog(QDialog):
+    """The welcome dialog.
+
+    The user can choose between creating a new project and loading a saved or
+    example project. When a project is loaded, it scans for validity of all
+    the required packages for the project, and in case some paths are invalid,
+    they are shown in the dialog. The user than can autodiscover those missing
+    packages or cherry-pick by choosing paths manually.
+
+    The user can also set some common configuration options, and can generate
+    an analogous command and config file and save the configuration in it.
     """
 
-    def __init__(self, init_window_theme_name='dark', parent=None):
+    def __init__(self, config: Config, parent=None):
+        """Initialize the `StartupDialog` class.
+
+        Parameters
+        ----------
+        config : Config
+            The global configuration, parsed from command line or run() function
+            interface.
+            Notice that this class operates on args directly, so all values
+            are either of primitive type (including strings), except paths
+            which are pathlib.Path objects. Translation to NodePackage objects,
+            WindowTheme objects etc. will happen at a later stage, not here.
+        parent : QWidget, optional
+            The parent `QWidget`.
+            The default is `None`.
+
+        Returns
+        -------
+        None.
+
+        """
         super().__init__(parent)
 
+        self.conf = config
+
+        #
+        # Layout the contents of the dialog
+        #
+
         layout = QVBoxLayout()
 
-        # info text edit
+        # Top info text edit
         info_text_edit = QTextEdit()
-        info_text_edit.setHtml('''
-            <center>
-                <h2 style="font-family: Segoe UI; font-size: xx-large; font-weight: 400; color: #a9d5ef;">
-                    Welcome to Ryven
-                </h2>
-            </center>
-            <div style="font-family: Corbel; font-size: x-large;">
-            
-                <p>
-                    <img style="float:right;" height=120 src="../resources/pics/Ryven_icon_blurred.png">Hey,
-                    it's Leon, the creator of Ryven. Keep in mind that this
-                    is not a professional piece of software. Don\'t forget to save!
-                    There are always some bugs and issues but as long as you keep behaving
-                    as intended, you shouldn\'t get into too much trouble. Have fun!
-                </p>
+        info_text_edit.setHtml(f'''
+            <div style="font-family: Corbel; font-size: large;">
+                <img style="float:right;" height=120 src="{abs_path_from_package_dir('resources/pics/Ryven_icon_blurred.png')}"
+                >Ryven is not a stable piece of software, it's experimental, and nothing is
+                guaranteed to work as expected. Make sure to save frequently, and to
+                different files. If you spot an issue, please report it on the 
+                <a href="https://github.com/leon-thomm/ryven/issues">GitHub page</a>.
+                <br><br>
+                Ryven doesn't come with batteries (nodes) included. It provides some
+                small examples but nothing more. Development of large node packages
+                is not part of the Ryven editor project itself.
+                See the GitHub for a quickstart guide.
+                Cheers.
             </div>
         ''')
         info_text_edit.setReadOnly(True)
         layout.addWidget(info_text_edit)
 
-        # buttons
-        plain_project_push_button = QPushButton('create new project')
-        plain_project_push_button.setFocus()
-        plain_project_push_button.clicked.connect(self.plain_project_button_clicked)
-        load_project_push_button = QPushButton('load project')
-        load_project_push_button.clicked.connect(self.load_project_button_clicked)
-        load_example_project_push_button = QPushButton('load example')
-        load_example_project_push_button.clicked.connect(self.load_example_project_button_clicked)
+        # The form with the configuration options
+        fbox = QFormLayout()
 
-        buttons_layout = QHBoxLayout()
-        buttons_layout.addWidget(plain_project_push_button)
-        buttons_layout.addWidget(load_project_push_button)
-        buttons_layout.addWidget(load_example_project_push_button)
+        # Project
+        project_label = QLabel('Project:')
 
+        project_layout = QVBoxLayout()
+        self.project_name = ElideLabel()
+        if self.conf.project is not None:
+            self.project_name.setText(str(config.project))
+        else:
+            self.project_name.setText(LBL_CREATE_PROJECT)
+        project_layout.addWidget(self.project_name)
+
+        project_buttons_widget = QDialogButtonBox()
+        self.create_project_button = QPushButton('New')
+        self.create_project_button.setToolTip('Create a new project')
+        self.create_project_button.setDefault(True)
+        self.create_project_button.clicked.connect(self.on_create_project_button_clicked)
+        project_buttons_widget.addButton(self.create_project_button, QDialogButtonBox.ActionRole)
+        load_project_button = QPushButton('Load')
+        load_project_button.setToolTip('Load an existing project')
+        load_project_button.clicked.connect(self.on_load_project_button_clicked)
+        project_buttons_widget.addButton(load_project_button, QDialogButtonBox.ActionRole)
+        load_example_project_button = QPushButton('Example')
+        load_example_project_button.setToolTip('Load a Ryven example')
+        load_example_project_button.clicked.connect(self.on_load_example_project_button_clicked)
+        project_buttons_widget.addButton(load_example_project_button, QDialogButtonBox.ActionRole)
+        project_layout.addWidget(project_buttons_widget)
+
+        fbox.addRow(project_label, project_layout)
+
+        # Nodes packages
+        packages_label = QLabel('Nodes packages:')
+
+        packages_layout = QVBoxLayout()
+        packages_sublayout = QHBoxLayout()
+
+        # FIXME: The three following columns should have half their default size!
+        # ???: How is that achieved?
+        packages_imported_layout = QVBoxLayout()
+        label_imported = QLabel('Imported:')
+        label_imported.setToolTip('Nodes packages which are required by the project and are found')
+        label_imported.setAlignment(Qt.AlignCenter)
+        packages_imported_layout.addWidget(label_imported)
+        self.imported_list_widget = QListWidget()
+        packages_imported_layout.addWidget(self.imported_list_widget)
+        packages_sublayout.addLayout(packages_imported_layout)
+
+        packages_missing_layout = QVBoxLayout()
+        label_missing = QLabel('Missing:')
+        label_missing.setToolTip('Nodes packages which are required by the project but could not be found')
+        label_missing.setAlignment(Qt.AlignCenter)
+        packages_missing_layout.addWidget(label_missing)
+        self.missing_list_widget = QListWidget()
+        packages_missing_layout.addWidget(self.missing_list_widget)
+        packages_sublayout.addLayout(packages_missing_layout)
+
+        packages_manually_layout = QVBoxLayout()
+        label_manually = QLabel('Manually imported:')
+        label_manually.setToolTip('Nodes packages which are manually imported\nThey will override the packages required by the project\nAdditional packages can be imported later …')
+        label_manually.setAlignment(Qt.AlignCenter)
+        packages_manually_layout.addWidget(label_manually)
+        self.manually_list_widget = QListWidget()
+        self.manually_list_widget.setSelectionMode(QListWidget.MultiSelection)
+        self.manually_list_widget.itemSelectionChanged.connect(self.on_packages_manually_selection)
+        packages_manually_layout.addWidget(self.manually_list_widget)
+        packages_sublayout.addLayout(packages_manually_layout)
+
+        packages_layout.addLayout(packages_sublayout)
+
+        packages_buttons_widget = QDialogButtonBox()
+        self.autodiscover_packages_button = QPushButton('Find')
+        self.autodiscover_packages_button.setToolTip('Automatically find and import missing packages')
+        self.autodiscover_packages_button.clicked.connect(self.on_autodiscover_package_clicked)
+        packages_buttons_widget.addButton(self.autodiscover_packages_button, QDialogButtonBox.ActionRole)
+        self.autodiscover_packages_button.setEnabled(False)
+        import_package_button = QPushButton('Import')
+        import_package_button.setToolTip('Manually load a nodes package')
+        import_package_button.clicked.connect(self.on_import_package_clicked)
+        packages_buttons_widget.addButton(import_package_button, QDialogButtonBox.ActionRole)
+        self.remove_packages_button = QPushButton('Remove')
+        self.remove_packages_button.setToolTip('Remove manually imported nodes packages')
+        self.remove_packages_button.clicked.connect(self.on_remove_packages_clicked)
+        self.remove_packages_button.setEnabled(False)
+        packages_buttons_widget.addButton(self.remove_packages_button, QDialogButtonBox.ActionRole)
+        self.clear_packages_button = QPushButton('Clear')
+        self.clear_packages_button.setToolTip('Clear the list of manually imported nodes packages ')
+        self.clear_packages_button.clicked.connect(self.on_clear_packages_clicked)
+        self.clear_packages_button.setEnabled(False)
+        packages_buttons_widget.addButton(self.clear_packages_button, QDialogButtonBox.ActionRole)
+        packages_layout.addWidget(packages_buttons_widget)
+
+        fbox.addRow(packages_label, packages_layout)
+
+        # Window theme
+        windowtheme_label = QLabel('Window theme:')
+        windowtheme_layout = QHBoxLayout()
+        windowtheme_button_group = QButtonGroup(windowtheme_layout)
+        self.window_theme_rbs = {
+            theme: QRadioButton(theme)
+            for theme in self.conf.get_available_window_themes()
+        }
+        for rb in self.window_theme_rbs.values():
+            windowtheme_button_group.addButton(rb)
+            windowtheme_layout.addWidget(rb)
+        windowtheme_button_group.buttonToggled.connect(self.on_window_theme_toggled)
+        fbox.addRow(windowtheme_label, windowtheme_layout)
+
+        # Flow theme
+        flowtheme_label = QLabel('Flow theme:')
+        flowtheme_widget = QComboBox()
+        flowtheme_widget.setToolTip('Select the theme of the flow display\nCan also be changed later …')
+        flowtheme_widget.addItems([LBL_DEFAULT_FLOW_THEME] + list(self.conf.get_available_flow_themes()))
+        flowtheme_widget.insertSeparator(1)
+        flowtheme_widget.currentTextChanged.connect(self.on_flow_theme_selected)
+        fbox.addRow(flowtheme_label, flowtheme_widget)
+
+        # Performance mode
+        performance_label = QLabel('Performance mode:')
+        performance_layout = QHBoxLayout()
+        performance_button_group = QButtonGroup(performance_layout)
+        self.perf_mode_rbs = {
+            mode: QRadioButton(mode)
+            for mode in self.conf.get_available_performance_modes()
+        }
+        for rb in self.perf_mode_rbs.values():
+            performance_button_group.addButton(rb)
+            performance_layout.addWidget(rb)
+        performance_button_group.buttonToggled.connect(self.on_performance_toggled)
+        fbox.addRow(performance_label, performance_layout)
+
+        # Animations
+        animations_label = QLabel('Animations:')
+        animations_cb = QCheckBox('Animations')
+        animations_cb.toggled.connect(self.on_animations_toggled)
+        fbox.addRow(animations_label, animations_cb)
+
+        # Title
+        title_label = QLabel('Window title:')
+        self.title_lineedit = QLineEdit()
+        self.title_lineedit.textChanged.connect(self.on_title_changed)
+        fbox.addRow(title_label, self.title_lineedit)
+
+        # Verbose
+        verbose_output_label = QLabel('Verbose:')
+        verbose_output_cb = QCheckBox('Enable verbose output')
+        verbose_output_cb.setToolTip(
+            f'''Choose whether verbose output should be displayed. 
+            Verbose output prevents stdout and stderr from being
+            displayed in the in-editor console, that usually means
+            all output goes to the terminal from which Ryven was
+            launched. Also, it causes lots of debug info to be 
+            printed.'''
+        )
+        verbose_output_cb.toggled.connect(self.on_verbose_toggled)
+        fbox.addRow(verbose_output_label, verbose_output_cb)
+
+        layout.addLayout(fbox)
+
+        # Buttons
+        buttons_layout = QHBoxLayout()
+        gen_config_button = QPushButton('generate / save config')
+        gen_config_button.clicked.connect(self.gen_config_clicked)
+        buttons_layout.addWidget(gen_config_button)
+        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+        self.ok_button = buttons.button(QDialogButtonBox.Ok)
+        buttons.accepted.connect(self.accept)
+        buttons.rejected.connect(self.reject)
+        buttons_layout.addWidget(buttons)
         layout.addLayout(buttons_layout)
 
-        self.window_theme = apply_stylesheet(init_window_theme_name)
+        self.setLayout(layout)
+
+        #
+        # Populate/set the widgets
+        #
 
-        choose_theme_layout = QHBoxLayout()
-        self.dark_theme_rb = QRadioButton('dark')
-        self.dark_theme_rb.toggled.connect(self.theme_toggled)
-        self.dark_theme_rb.setChecked(init_window_theme_name == 'dark')
-        self.light_theme_rb = QRadioButton('light')
-        self.light_theme_rb.toggled.connect(self.theme_toggled)
-        self.light_theme_rb.setChecked(init_window_theme_name == 'light')
-        choose_theme_layout.addWidget(self.dark_theme_rb)
-        choose_theme_layout.addWidget(self.light_theme_rb)
-        layout.addLayout(choose_theme_layout)
+        # Set project
+        self.load_project(self.conf.project)
 
-        self.setLayout(layout)
+        # Set requested nodes
+        self.update_packages_lists()
+
+        # Set window theme
+        for theme, rb in self.window_theme_rbs.items():
+            rb.setChecked(self.conf.window_theme == theme)
+
+        # Set performance mode
+        for mode, rb in self.perf_mode_rbs.items():
+            rb.setChecked(self.conf.performance_mode == mode)
 
+        # Set animations
+        animations_cb.setChecked(self.conf.animations)
+
+        # Set title
+        self.title_lineedit.setText(self.conf.window_title)
+
+        # Set flow theme
+        if self.conf.flow_theme is not None:
+            idx = flowtheme_widget.findText(self.conf.flow_theme)
+        else:
+            idx = flowtheme_widget.findText(LBL_DEFAULT_FLOW_THEME)
+        flowtheme_widget.setCurrentIndex(idx)
+
+        # Set verbose output
+        verbose_output_cb.setChecked(self.conf.verbose)
+
+        # Set window title and icon
         self.setWindowTitle('Ryven')
         self.setWindowIcon(QIcon(abs_path_from_package_dir('resources/pics/Ryven_icon.png')))
-        self.setFixedSize(500, 280)
 
-        self.editor_startup_configuration = {}
+    #
+    # Call-back methods
+    #
+
+    # Project
+
+    def on_create_project_button_clicked(self):
+        """Call-back method, whenever the 'New' button was clicked."""
+        # Create a new project
+        self.load_project(None)
+
+    def on_load_project_button_clicked(self):
+        """Call-back method, whenever the 'Load' button was clicked."""
+        # Load a saved project, starting in the user's ryven directory
+        project_path = self.get_project(abs_path_from_ryven_dir('saves'))
+
+        if project_path is not None:
+            self.load_project(project_path)
 
+    def on_load_example_project_button_clicked(self):
+        """Call-back method, whenever the 'Example' button was clicked."""
+        # Load an example project, starting in the ryven's example directory
+        project_path = self.get_project(
+            abs_path_from_package_dir('examples_projects'),
+            title='Select Ryven example')
 
-    def theme_toggled(self):
-        if self.dark_theme_rb.isChecked():
-            self.window_theme = apply_stylesheet('dark')
+        if project_path is not None:
+            self.load_project(project_path)
+
+    # Nodes packages
+
+    def on_packages_manually_selection(self):
+        """Call-back method, whenever a package in the manual list was selected."""
+        # En/Disable the 'Remove' button
+        if self.manually_list_widget.selectedItems():
+            self.remove_packages_button.setEnabled(True)
+        else:
+            self.remove_packages_button.setEnabled(False)
+
+    def on_autodiscover_package_clicked(self):
+        """Call-back method, whenever the 'Find' button was clicked."""
+        # Search in user packages
+        self.auto_discover(pathlib.Path(ryven_dir_path(), 'nodes'))
+        self.update_packages_lists()
+
+        # Search in example packages
+        self.auto_discover(pathlib.Path(abs_path_from_package_dir('example_nodes')))
+        self.update_packages_lists()
+
+    def on_import_package_clicked(self):
+        """Call-back method, whenever the 'Import' button was clicked."""
+        # Import a nodes package, starting in the user's ryven directory
+        file_name = QFileDialog.getOpenFileName(
+            self, 'Select',
+            abs_path_from_ryven_dir('packages'),
+            'ryven nodes package (nodes.py)')[0]
+
+        if file_name:
+            file_path = pathlib.Path(file_name)
+            if file_path.exists():
+                self.conf.nodes.add(file_path.parent)
+                self.update_packages_lists()
+
+    def on_remove_packages_clicked(self):
+        """Call-back method, whenever the 'Remove' button was clicked."""
+        # Remove packages selected in the manual imported list
+        for item in self.manually_list_widget.selectedItems():
+            package_name = item.text()
+            for pkg_path in self.conf.nodes:
+                if pkg_path.stem == package_name:
+                    self.conf.nodes.remove(pkg_path)
+                    break
+
+        self.update_packages_lists()
+
+    def on_clear_packages_clicked(self):
+        """Call-back method, for when the 'Clear' button was clicked."""
+        # Clear the manual imported list
+        self.conf.nodes.clear()
+
+        self.update_packages_lists()
+
+    # Window theme
+
+    def on_window_theme_toggled(self):
+        """Call-back method, whenever a window theme radio button was toggled."""
+        # Apply the selected window theme
+        for theme, rb in self.window_theme_rbs.items():
+            if rb.isChecked():
+                self.conf.window_theme = theme
+                break
+
+        # Set the StartupDialog to the selected theme
+        apply_stylesheet(self.conf.window_theme)
+
+    # Flow theme
+
+    def on_flow_theme_selected(self, theme):
+        """Call-back method, whenever a new flow theme was selected."""
+        # "Apply" the selected flow theme
+        if theme == LBL_DEFAULT_FLOW_THEME:
+            self.conf.flow_theme = Config.flow_theme
         else:
-            self.window_theme = apply_stylesheet('light')
+            self.conf.flow_theme = theme
 
+    # Performance mode
 
-    def plain_project_button_clicked(self):
-        self.editor_startup_configuration['action'] = 'create project'
-        self.accept()
+    def on_performance_toggled(self):
+        """Call-back method, whenever a new performance mode was selected"""
+        for mode, rb in self.perf_mode_rbs.items():
+            if rb.isChecked():
+                self.conf.performance_mode = mode
+                break
 
+    # Animations
 
-    def load_project_button_clicked(self):
-        self.open_project(base_dir=abs_path_from_ryven_dir('saves'))
+    def on_animations_toggled(self, check):
+        """Call-back method, whenever animations are enabled/disabled"""
+        self.conf.animations = check
 
+    # Title
 
-    def load_example_project_button_clicked(self):
-        self.open_project(base_dir=abs_path_from_package_dir('examples_projects'))
+    def on_title_changed(self, t):
+        """Call-back method, whenever the title was changed"""
+        self.conf.window_title = t
 
+    # Verbose output
 
-    def open_project(self, base_dir: str):
-        """1. Opens a file dialog for selecting a project tile; returns on FileNotFoundError,
-        2. Opens the project and scans all required node packages,
-        3. If node packages are missing (i.e. they cannot be found under the path specified
-        in the project file anymore) it opens the SelectPackages_Dialog
+    def on_verbose_toggled(self, check):
+        """Call-back method, whenever the verbose checkbox was toggled."""
+        # "Apply" the verbose option
+        self.conf.verbose = check
 
-        :param base_dir: path to directory where file dialog opens
-        """
+    #
+    # Helper/Working methods
+    #
+
+    def get_project(self, base_dir: str, title='Select project file') -> Optional[pathlib.Path]:
+        """Get a project file from the user.
 
-        self.editor_startup_configuration['action'] = 'open project'
-        import json
+        Parameters
+        ----------
+        base_dir : str|pathlib.Path
+            The initial directory shown in the file dialog.
+        title : str, optional
+            The title of the file dialog.
+            The default is 'Select project file'.
 
-        file_name = \
-            QFileDialog.getOpenFileName(
-                self, 'select project file',
-                base_dir, 'JSON (*.json)'
+        Returns
+        -------
+        file_path : pathlib.Path|None
+            The path of the selected file.
+
+        """
+        # Get the file from the user
+        file_name = QFileDialog.getOpenFileName(
+            self, title,
+            str(base_dir), 'JSON (*.json)'
             )[0]
 
-        try:
-            f = open(file_name)
-            project_str = f.read()
-            f.close()
-        except FileNotFoundError:
-            # TODO: do something useful here
-            return
-
-        # strict=False has to be to allow 'control characters' like '\n' for newline when loading the json
-        project_dict = json.loads(project_str, strict=False)
-
-        # scan for all required packages
-        valid_node_packages = []
-        missing_node_package_names = []
-        for p in project_dict['required packages']:
-            try:
-                f = open(p['dir']+'/nodes.py')
-                f.close()
-                valid_node_packages.append(NodesPackage(p['dir']))
-            except FileNotFoundError:
-                missing_node_package_names.append(p['name'])
-
-        node_packages = valid_node_packages.copy()
-
-        if len(missing_node_package_names) > 0:
-            select_packages_dialog = SelectPackages_Dialog(self, required_packages=missing_node_package_names)
-            select_packages_dialog.exec_()
-            node_packages += select_packages_dialog.packages
-
-        self.editor_startup_configuration['required packages'] = node_packages
-        self.editor_startup_configuration['content'] = project_dict
-
-        self.accept()
+        if file_name:
+            file_path = pathlib.Path(file_name)
+            if file_path.exists():
+                return file_path
+
+        return None
+
+    def load_project(self, project_path: Optional[pathlib.Path]):
+        """Load a project file.
+
+        It opens the project file and scans for all required node packages.
+        These are displayed in the imported packages list. All packages which
+        could not be found are displayed in the missing packages list.
+
+        Parameters
+        ----------
+        project_path : pathlib.Path
+            The project's file name to be loaded.
+
+        Returns
+        -------
+        None.
+
+        """
+
+        self.imported_list_widget.clear()
+        self.missing_list_widget.clear()
+
+        if project_path is None:
+            self.conf.project = None
+            self.project_name.setText(LBL_CREATE_PROJECT)
+            self.create_project_button.setEnabled(False)
+
+        else:
+            self.conf.project = project_path
+            self.project_name.setText(project_path)
+            self.create_project_button.setEnabled(True)
+
+            required_nodes, missing_nodes, _ = process_nodes_packages(project_path)
+            item_flags = ~Qt.ItemIsSelectable & ~Qt.ItemIsEditable
+            for node in sorted(required_nodes, key=lambda n: n.name):
+                node_item = QListWidgetItem(node.name)
+                node_item.setToolTip(node.directory)
+                node_item.setFlags(item_flags)
+                self.imported_list_widget.addItem(node_item)
+            for node_path in sorted(missing_nodes, key=lambda p: p.name):
+                node_item = QListWidgetItem(node_path.name)
+                node_item.setToolTip(str(node_path))
+                node_item.setFlags(item_flags)
+                self.missing_list_widget.addItem(node_item)
+
+        self.update_packages_lists()
+
+    def auto_discover(self, packages_dir):
+        """Automatically find and import missing packages.
+
+        Parameters
+        ----------
+        packages_dir : str|pathlib.Path
+            The directory under which packages should be searched.
+
+        Returns
+        -------
+        None.
+
+        """
+        # List of packages which are missing
+        missing_packages = [
+            self.missing_list_widget.item(i).text()
+            for i in range(self.missing_list_widget.count())]
+
+        # Search for missing packages under `package_dir`
+        for top, dirs, files in os.walk(packages_dir):
+            path = pathlib.Path(top)
+            if path.name in missing_packages:
+                node_path = path.joinpath('nodes.py')
+                if node_path.exists():
+                    self.conf.nodes.add(path)
+
+    def update_packages_lists(self):
+        """Update the packages lists and buttons.
+
+        1. Mark all imported packages, if they were manually imported.
+        2. Mark all missing packages, if they were manually imported.
+        3. Repopulate the list with manually imported packages.
+        4. En/Disable 'Ok', 'Find' and 'Clear' buttons.
+        """
+        # Mark all imported packages, if they were manually imported
+        for i in range(self.imported_list_widget.count()):
+            node_item = self.imported_list_widget.item(i)
+            font = node_item.font()
+            for pkg_path in self.conf.nodes:
+                if node_item.text() == pkg_path.stem:
+                    # Required package is overwritten by manually imported package
+                    font.setStrikeOut(True)
+                    break
+            else:
+                font.setStrikeOut(False)
+            node_item.setFont(font)
+
+        # Mark all missing packages, if they were manually imported
+        missing_packages = False         # Track, if we have to enable the 'Find' button
+        for i in range(self.missing_list_widget.count()):
+            node_item = self.missing_list_widget.item(i)
+            font = node_item.font()
+            for pkg_path in self.conf.nodes:
+                if node_item.text() == pkg_path.stem:
+                    # Missing package is provided by manually imported package
+                    font.setStrikeOut(True)
+                    break
+            else:
+                font.setStrikeOut(False)
+                missing_packages = True
+            node_item.setFont(font)
+
+        # Repopulate the list with manually imported packages
+        self.manually_list_widget.clear()
+        for pkg_path in sorted(self.conf.nodes):
+            node_item = QListWidgetItem(pkg_path.stem)
+            node_item.setToolTip(str(pkg_path))
+            node_item.setFlags(~Qt.ItemIsEditable)
+            self.manually_list_widget.addItem(node_item)
+
+        # Update the buttons
+        if missing_packages:
+            # There are still packages missing
+            self.ok_button.setEnabled(False)
+            self.ok_button.setToolTip('Import all missing packages first')
+            self.autodiscover_packages_button.setEnabled(True)
+        else:
+            # No missing packages
+            self.ok_button.setEnabled(True)
+            self.ok_button.setToolTip(None)
+            self.autodiscover_packages_button.setEnabled(False)
+        self.clear_packages_button.setEnabled(bool(self.conf.nodes))
+
+    def gen_config_clicked(self):
+        """Generates the command analogous to the specified settings
+        as well as the according config file content.
+        Opens a dialog with option to save to config file.
+        """
+
+        # Convert configuration dictionary to command line argument and
+        # configuration file contents
+        command, config = unparse_sys_args(self.conf)
+
+        d = ShowCommandDialog(command, config)
+        d.exec_()
diff --git a/ryven/gui/std_input_widgets.py b/ryven/gui/std_input_widgets.py
new file mode 100644
index 00000000..992ef0ec
--- /dev/null
+++ b/ryven/gui/std_input_widgets.py
@@ -0,0 +1,353 @@
+"""
+This module provides some frequently useful convenience input widgets for nodes.
+It replaces what was previously the dtypes system.
+"""
+from typing import Tuple
+
+from ryvencore import Data
+
+from ryvencore_qt import NodeInputWidget
+
+from qtpy.QtWidgets import QLineEdit, QSpinBox, QCheckBox, QSlider
+from qtpy.QtGui import QFont, QFontMetrics, Qt
+
+
+class StdInputWidgetBase(NodeInputWidget):
+    def __init__(self, params):
+        super().__init__(params)
+
+        self._prevent_update = PreventUpdateCtx(False)
+
+    @property
+    def val(self) -> Data:
+        raise NotImplementedError
+
+    def load_from(self, val: Data):
+        raise NotImplementedError
+
+    def on_widget_val_changed(self, val: Data):
+        """
+        This will update the input's default value, and also update
+        the node unless it is called in a `with self._prevent_update`
+        block.
+        """
+        self.update_node_input(val, silent=self._prevent_update.blocked)
+
+    def get_state(self) -> dict:
+        return {'val': self.val}
+
+    def set_state(self, data: dict):
+        with self._prevent_update:
+            self.load_from(data['val'])
+
+
+class PreventUpdateCtx:
+    def __init__(self, initial: bool):
+        self._blocked: bool = initial
+
+    @property
+    def blocked(self) -> bool:
+        return self._blocked
+
+    def __enter__(self):
+        self._blocked = True
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._blocked = False
+
+
+class Builder:
+    """
+    Provides functions for creating parametrized input widgets.
+    """
+
+    @staticmethod
+    def evaled_line_edit(init=None, size='m', descr: str = '', resizing: bool = True, data_type: type[Data] = Data):
+        """
+        Creates a line edit input widget which evaluates its input.
+        :param init: the initial value shown in the widget
+        :param descr: the description of the input
+        :param size: 's', 'm' or 'l'
+        """
+
+        if size == 's':
+            base_width = 30
+        elif size == 'm':
+            base_width = 70
+        else:  # size == 'l':
+            base_width = 150
+        max_width = base_width * 3 if resizing else base_width
+
+        class StdInpWidget_EvaledLineEdit(StdInputWidgetBase, QLineEdit):
+            def __init__(self, params):
+                StdInputWidgetBase.__init__(self, params)
+                QLineEdit.__init__(self)
+
+                # set size
+                self.base_width = base_width
+                self.max_width = max_width
+                self.setFixedWidth(self.base_width)
+
+                # font
+                self.setFont(QFont('source code pro', 10))
+                self.fm = QFontMetrics(self.font())
+
+                # tooltip
+                self.setToolTip(self.__doc__)
+
+                self.textChanged.connect(self.text_changed)
+                self.returnPressed.connect(self.return_pressed)
+
+                # initial value
+                with self._prevent_update:
+                    self.setText(str(init))
+
+            def return_pressed(self):
+                self.on_widget_val_changed(self.val)
+
+            def text_changed(self, new_text):
+                """Manages resizing of the widget to content."""
+                if resizing:
+                    text_width = self.fm.width(new_text)
+                    new_width = text_width + 15  # add some buffer
+                    self.setFixedWidth(min(
+                        max(new_width, self.base_width),
+                        self.max_width
+                    ))
+                    self.node_gui.update_shape()
+                self.on_widget_val_changed(self.val)
+
+            @property
+            def val(self) -> data_type:
+                try:
+                    return data_type(eval(self.text()))
+                except:
+                    return data_type(self.text())
+
+            def load_from(self, val: Data):
+                with self._prevent_update:
+                    self.setText(str(val.payload))
+
+            def val_update_event(self, val: Data):
+                # represent incoming data
+                try:
+                    with self._prevent_update:
+                        self.setText(str(val.payload))
+                except:
+                    self.setText("<can't stringify>")
+
+        StdInpWidget_EvaledLineEdit.__doc__ = descr
+
+        return StdInpWidget_EvaledLineEdit
+
+    @staticmethod
+    def str_line_edit(init: str = '', size='m', descr: str = '', resizing: bool = True, data_type: type[Data] = Data):
+        """
+        Creates a line edit input widget which evaluates its input.
+        :param init: the initial value shown in the widget
+        :param descr: the description of the input
+        :param size: 's', 'm' or 'l'
+        """
+
+        if size == 's':
+            base_width = 30
+        elif size == 'm':
+            base_width = 70
+        else:  # size == 'l':
+            base_width = 150
+        max_width = base_width * 3 if resizing else base_width
+
+        class StdInpWidget_StrLineEdit(StdInputWidgetBase, QLineEdit):
+            def __init__(self, params):
+                StdInputWidgetBase.__init__(self, params)
+                QLineEdit.__init__(self)
+
+                # set size
+                self.base_width = base_width
+                self.max_width = max_width
+                self.setFixedWidth(self.base_width)
+
+                # font
+                self.setFont(QFont('source code pro', 10))
+                self.fm = QFontMetrics(self.font())
+
+                # tooltip
+                self.setToolTip(self.__doc__)
+
+                self.textChanged.connect(self.text_changed)
+                self.returnPressed.connect(self.return_pressed)
+
+                # initial value
+                with self._prevent_update:
+                    self.setText(str(init))
+
+            def return_pressed(self):
+                self.on_widget_val_changed(self.val)
+
+            def text_changed(self, new_text):
+                """Manages resizing of the widget to content."""
+                if resizing:
+                    text_width = self.fm.width(new_text)
+                    new_width = text_width + 15  # add some buffer
+                    self.setFixedWidth(min(
+                        max(new_width, self.base_width),
+                        self.max_width
+                    ))
+                    self.node_gui.update_shape()
+                self.on_widget_val_changed(self.val)
+
+            @property
+            def val(self) -> data_type:
+                return data_type(self.text())
+
+            def load_from(self, val: Data):
+                with self._prevent_update:
+                    self.setText(str(val.payload))
+
+            def val_update_event(self, val: Data):
+                # represent incoming data
+                try:
+                    with self._prevent_update:
+                        self.setText(str(val.payload))
+                except:
+                    self.setText("<can't stringify>")
+
+        StdInpWidget_StrLineEdit.__doc__ = descr
+
+        return StdInpWidget_StrLineEdit
+
+    @staticmethod
+    def int_spinbox(init: int = 0, range: Tuple[int, int] = (0, 99), descr: str = '', data_type: type[Data] = Data):
+        """
+        Creates a spinbox input widget for integers.
+        :param init: the initial value shown in the widget
+        :param range: the range of the spinbox
+        :param descr: the description of the input
+        """
+
+        class StdInpWidget_IntSpinBox(StdInputWidgetBase, QSpinBox):
+
+            def __init__(self, params):
+                StdInputWidgetBase.__init__(self, params)
+                QSpinBox.__init__(self)
+
+                # there is no 'valueEdited' signal, only a 'valueChanged' signal
+                # so we need to block the signal when a new value is coming from
+                # a connection
+                self._prevent_update = PreventUpdateCtx(False)
+
+                # tooltip
+                self.setToolTip(self.__doc__)
+
+                self.valueChanged.connect(self.value_changed)
+
+                # initial value and rage
+                with self._prevent_update:
+                    self.setRange(*range)
+                    self.setValue(init)
+
+
+            @property
+            def val(self) -> data_type:
+                return data_type(self.value())
+
+            def load_from(self, val: Data):
+                with self._prevent_update:
+                    self.setValue(val.payload)
+
+            def value_changed(self, _):
+                self.on_widget_val_changed(self.val)
+
+            def val_update_event(self, val: Data):
+                if isinstance(val.payload, int):
+                    with self._prevent_update:
+                        self.setValue(val.payload)
+
+        StdInpWidget_IntSpinBox.__doc__ = descr
+
+        return StdInpWidget_IntSpinBox
+
+    @staticmethod
+    def int_slider(init: int = 0, range: Tuple[int, int] = (0, 10), descr: str = '', data_type: type[Data] = Data):
+        """
+        Creates a slider input widget for ints.
+        :param init: the initial value shown in the widget
+        :param range: the range of the slider
+        :param descr: the description of the input
+        """
+
+        class StdInpWidget_IntSlider(StdInputWidgetBase, QSlider):
+            def __init__(self, params):
+                StdInputWidgetBase.__init__(self, params)
+                QSlider.__init__(self, Qt.Horizontal)
+
+                # tooltip
+                self.setToolTip(self.__doc__)
+
+                self.valueChanged.connect(self.value_changed)
+
+                # initial value and rage
+                with self._prevent_update:
+                    self.setRange(*range)
+                    self.setValue(init)
+
+            @property
+            def val(self) -> data_type:
+                return data_type(self.value())
+
+            def load_from(self, val: Data):
+                with self._prevent_update:
+                    self.setValue(val.payload)
+
+            def value_changed(self, _):
+                self.on_widget_val_changed(self.val)
+
+            def val_update_event(self, val: Data):
+                if isinstance(val.payload, int):
+                    self.setValue(val.payload)
+
+        StdInpWidget_IntSlider.__doc__ = descr
+
+        return StdInpWidget_IntSlider
+
+    @staticmethod
+    def bool_checkbox(init: bool = False, descr: str = '', data_type: type[Data] = Data):
+        """
+        Creates a checkbox input widget for booleans.
+        :param init: the initial value shown in the widget
+        :param descr: the description of the input
+        """
+
+        class StdInpWidget_BoolCheckBox(StdInputWidgetBase, QCheckBox):
+            def __init__(self, params):
+                StdInputWidgetBase.__init__(self, params)
+                QCheckBox.__init__(self)
+
+                # tooltip
+                self.setToolTip(self.__doc__)
+
+                self.stateChanged.connect(self.state_changed)
+
+                # initial value
+                with self._prevent_update:
+                    self.setChecked(init)
+
+            @property
+            def val(self) -> data_type:
+                return data_type(self.isChecked())
+
+            def load_from(self, val: Data):
+                with self._prevent_update:
+                    self.setChecked(val.payload)
+
+            def state_changed(self, _):
+                self.on_widget_val_changed(self.val)
+
+            def val_update_event(self, val: Data):
+                if isinstance(val.payload, bool):
+                    with self._prevent_update:
+                        self.setChecked(val.payload)
+
+        StdInpWidget_BoolCheckBox.__doc__ = descr
+
+        return StdInpWidget_BoolCheckBox
diff --git a/ryven/gui/styling/window_theme.py b/ryven/gui/styling/window_theme.py
index ca6dce12..ed6b7cdf 100644
--- a/ryven/gui/styling/window_theme.py
+++ b/ryven/gui/styling/window_theme.py
@@ -3,7 +3,10 @@
 
 
 def hex_to_rgb(hex: str):
-    return tuple(int(hex[i:i + 2], 16) for i in (1, 3, 5))
+    if hex is None:
+        return None
+    else:
+        return tuple(int(hex[i:i + 2], 16) for i in (1, 3, 5))
 
 
 class WindowTheme:
@@ -63,34 +66,53 @@ class WindowTheme_Light(WindowTheme):
     }
 
 
+class WindowTheme_Plain(WindowTheme):
+    name = 'plain'
+    colors = {
+        'primaryColor': None,
+        'primaryLightColor': None,
+        'secondaryColor': None,
+        'secondaryLightColor': None,
+        'secondaryDarkColor': None,
+        'primaryTextColor': None,
+        'secondaryTextColor': None,
+        'danger': None,
+        'warning': None,
+        'success': None,
+    }
+
+
 def apply_stylesheet(style: str):
 
     # set to None if not used
     icons_dir = abs_path_from_package_dir('resources/stylesheets/icons')
-
-    # path to the template stylesheet file
-    template_file = abs_path_from_package_dir('resources/stylesheets/style_template.css')
-
-    # ------------------------------
-
     if icons_dir is not None:
         from qtpy.QtCore import QDir
         d = QDir()
         d.setSearchPaths('icon', [icons_dir])
 
-    if style == 'dark':
-        window_theme = WindowTheme_Dark()
+    if style in (None, 'plain'):
+        window_theme = WindowTheme_Plain()
+        stylesheet = None
     else:
-        window_theme = WindowTheme_Light()
-
-    f = open(template_file)
-
-    from jinja2 import Template
-    jinja_template = Template(f.read())
-
-    f.close()
+        if style == 'dark':
+            window_theme = WindowTheme_Dark()
+        elif style == 'light':
+            window_theme = WindowTheme_Light()
+        else:
+            raise ValueError(
+                f'Unknown window theme. '
+                f'Got: {style}')
+
+        from jinja2 import Template
+        # path to the template stylesheet file
+        template_file = abs_path_from_package_dir('resources/stylesheets/style_template.css')
+        with open(template_file) as f:
+            jinja_template = Template(f.read())
+
+        stylesheet = jinja_template.render(window_theme.rules)
 
     app = QApplication.instance()
-    app.setStyleSheet(jinja_template.render(window_theme.rules))
+    app.setStyleSheet(stylesheet)
 
     return window_theme
diff --git a/ryven/gui/uic/script.ui b/ryven/gui/uic/flow.ui
similarity index 92%
rename from ryven/gui/uic/script.ui
rename to ryven/gui/uic/flow.ui
index 24e22950..5a39dc50 100644
--- a/ryven/gui/uic/script.ui
+++ b/ryven/gui/uic/flow.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>script_widget</class>
- <widget class="QWidget" name="script_widget">
+ <class>flow_widget</class>
+ <widget class="QWidget" name="flow_widget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -42,8 +42,8 @@
              <rect>
               <x>0</x>
               <y>0</y>
-              <width>297</width>
-              <height>812</height>
+              <width>544</width>
+              <height>818</height>
              </rect>
             </property>
             <layout class="QHBoxLayout" name="horizontalLayout_3"/>
@@ -107,8 +107,4 @@
  </widget>
  <resources/>
  <connections/>
- <buttongroups>
-  <buttongroup name="algorithm_buttonGroup"/>
-  <buttongroup name="viewport_update_mode_buttonGroup"/>
- </buttongroups>
 </ui>
diff --git a/ryven/gui/uic/main_window.ui b/ryven/gui/uic/main_window.ui
index 04479957..5e61dcdd 100644
--- a/ryven/gui/uic/main_window.ui
+++ b/ryven/gui/uic/main_window.ui
@@ -34,9 +34,9 @@
         <property name="orientation">
          <enum>Qt::Vertical</enum>
         </property>
-        <widget class="QGroupBox" name="scripts_groupBox">
+        <widget class="QGroupBox" name="flows_groupBox">
          <property name="title">
-          <string>Scripts</string>
+          <string>Flows</string>
          </property>
          <layout class="QVBoxLayout" name="verticalLayout_2"/>
         </widget>
@@ -47,7 +47,7 @@
          <layout class="QVBoxLayout" name="verticalLayout"/>
         </widget>
        </widget>
-       <widget class="QTabWidget" name="scripts_tab_widget">
+       <widget class="QTabWidget" name="flows_tab_widget">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
           <horstretch>0</horstretch>
@@ -75,7 +75,7 @@
      <x>0</x>
      <y>0</y>
      <width>1368</width>
-     <height>21</height>
+     <height>30</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuFile">
@@ -86,9 +86,9 @@
      <property name="title">
       <string>Scripts</string>
      </property>
-     <addaction name="actionNew_Script"/>
-     <addaction name="actionRename_Script"/>
-     <addaction name="actionDelete_Script"/>
+     <addaction name="actionNew_Flow"/>
+     <addaction name="actionRename_Flow"/>
+     <addaction name="actionDelete_Flow"/>
     </widget>
     <addaction name="actionImport_Nodes"/>
     <addaction name="actionImport_Example_Nodes"/>
@@ -182,17 +182,17 @@ The more you zoomed in, the sharper the picture.
 This will take a few seconds.</string>
    </property>
   </action>
-  <action name="actionNew_Script">
+  <action name="actionNew_Flow">
    <property name="text">
     <string>New</string>
    </property>
   </action>
-  <action name="actionRename_Script">
+  <action name="actionRename_Flow">
    <property name="text">
     <string>Rename</string>
    </property>
   </action>
-  <action name="actionDelete_Script">
+  <action name="actionDelete_Flow">
    <property name="text">
     <string>Delete</string>
    </property>
diff --git a/ryven/gui/uic/ui_script.py b/ryven/gui/uic/ui_flow.py
similarity index 81%
rename from ryven/gui/uic/ui_script.py
rename to ryven/gui/uic/ui_flow.py
index c0ab6b02..01bf2f2a 100644
--- a/ryven/gui/uic/ui_script.py
+++ b/ryven/gui/uic/ui_flow.py
@@ -1,26 +1,26 @@
 # -*- coding: utf-8 -*-
 
 ################################################################################
-## Form generated from reading UI file 'script.ui'
+## Form generated from reading UI file 'flow.ui'
 ##
 ## Created by: Qt User Interface Compiler version 5.15.2
 ##
 ## WARNING! All changes made in this file will be lost when recompiling UI file!
 ################################################################################
 
-from PySide2.QtCore import *
-from PySide2.QtGui import *
-from PySide2.QtWidgets import *
+from qtpy.QtCore import *
+from qtpy.QtGui import *
+from qtpy.QtWidgets import *
 
 
-class Ui_script_widget(object):
-    def setupUi(self, script_widget):
-        if not script_widget.objectName():
-            script_widget.setObjectName(u"script_widget")
-        script_widget.resize(1223, 876)
-        self.gridLayout_4 = QGridLayout(script_widget)
+class Ui_flow_widget(object):
+    def setupUi(self, flow_widget):
+        if not flow_widget.objectName():
+            flow_widget.setObjectName(u"flow_widget")
+        flow_widget.resize(1223, 876)
+        self.gridLayout_4 = QGridLayout(flow_widget)
         self.gridLayout_4.setObjectName(u"gridLayout_4")
-        self.splitter_3 = QSplitter(script_widget)
+        self.splitter_3 = QSplitter(flow_widget)
         self.splitter_3.setObjectName(u"splitter_3")
         self.splitter_3.setOrientation(Qt.Horizontal)
         self.splitter = QSplitter(self.splitter_3)
@@ -38,7 +38,7 @@ def setupUi(self, script_widget):
         self.logs_scrollArea.setWidgetResizable(True)
         self.scrollAreaWidgetContents = QWidget()
         self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
-        self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 297, 812))
+        self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 544, 818))
         self.horizontalLayout_3 = QHBoxLayout(self.scrollAreaWidgetContents)
         self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
         self.logs_scrollArea.setWidget(self.scrollAreaWidgetContents)
@@ -89,16 +89,16 @@ def setupUi(self, script_widget):
         self.gridLayout_4.addWidget(self.splitter_3, 0, 0, 1, 1)
 
 
-        self.retranslateUi(script_widget)
+        self.retranslateUi(flow_widget)
 
-        QMetaObject.connectSlotsByName(script_widget)
+        QMetaObject.connectSlotsByName(flow_widget)
     # setupUi
 
-    def retranslateUi(self, script_widget):
-        script_widget.setWindowTitle(QCoreApplication.translate("script_widget", u"Form", None))
-        self.log_groupBox.setTitle(QCoreApplication.translate("script_widget", u"Log", None))
-        self.source_code_groupBox.setTitle(QCoreApplication.translate("script_widget", u"Source Code", None))
-        self.settings_groupBox.setTitle(QCoreApplication.translate("script_widget", u"Settings", None))
-        self.variables_group_box.setTitle(QCoreApplication.translate("script_widget", u"Variables", None))
+    def retranslateUi(self, flow_widget):
+        flow_widget.setWindowTitle(QCoreApplication.translate("flow_widget", u"Form", None))
+        self.log_groupBox.setTitle(QCoreApplication.translate("flow_widget", u"Log", None))
+        self.source_code_groupBox.setTitle(QCoreApplication.translate("flow_widget", u"Source Code", None))
+        self.settings_groupBox.setTitle(QCoreApplication.translate("flow_widget", u"Settings", None))
+        self.variables_group_box.setTitle(QCoreApplication.translate("flow_widget", u"Variables", None))
     # retranslateUi
 
diff --git a/ryven/gui/uic/ui_main_window.py b/ryven/gui/uic/ui_main_window.py
index 9a0eb51c..55ed29cb 100644
--- a/ryven/gui/uic/ui_main_window.py
+++ b/ryven/gui/uic/ui_main_window.py
@@ -39,12 +39,12 @@ def setupUi(self, MainWindow):
         self.actionSave_Pic_Viewport.setObjectName(u"actionSave_Pic_Viewport")
         self.actionSave_Pic_Whole_Scene_scaled = QAction(MainWindow)
         self.actionSave_Pic_Whole_Scene_scaled.setObjectName(u"actionSave_Pic_Whole_Scene_scaled")
-        self.actionNew_Script = QAction(MainWindow)
-        self.actionNew_Script.setObjectName(u"actionNew_Script")
-        self.actionRename_Script = QAction(MainWindow)
-        self.actionRename_Script.setObjectName(u"actionRename_Script")
-        self.actionDelete_Script = QAction(MainWindow)
-        self.actionDelete_Script.setObjectName(u"actionDelete_Script")
+        self.actionNew_Flow = QAction(MainWindow)
+        self.actionNew_Flow.setObjectName(u"actionNew_Flow")
+        self.actionRename_Flow = QAction(MainWindow)
+        self.actionRename_Flow.setObjectName(u"actionRename_Flow")
+        self.actionDelete_Flow = QAction(MainWindow)
+        self.actionDelete_Flow.setObjectName(u"actionDelete_Flow")
         self.actionImport_Example_Nodes = QAction(MainWindow)
         self.actionImport_Example_Nodes.setObjectName(u"actionImport_Example_Nodes")
         self.centralWidget = QWidget(MainWindow)
@@ -62,13 +62,13 @@ def setupUi(self, MainWindow):
         self.splitter = QSplitter(self.main_horizontal_splitter)
         self.splitter.setObjectName(u"splitter")
         self.splitter.setOrientation(Qt.Vertical)
-        self.scripts_groupBox = QGroupBox(self.splitter)
-        self.scripts_groupBox.setObjectName(u"scripts_groupBox")
-        self.verticalLayout_2 = QVBoxLayout(self.scripts_groupBox)
+        self.flows_groupBox = QGroupBox(self.splitter)
+        self.flows_groupBox.setObjectName(u"flows_groupBox")
+        self.verticalLayout_2 = QVBoxLayout(self.flows_groupBox)
         self.verticalLayout_2.setSpacing(6)
         self.verticalLayout_2.setContentsMargins(11, 11, 11, 11)
         self.verticalLayout_2.setObjectName(u"verticalLayout_2")
-        self.splitter.addWidget(self.scripts_groupBox)
+        self.splitter.addWidget(self.flows_groupBox)
         self.nodes_groupBox = QGroupBox(self.splitter)
         self.nodes_groupBox.setObjectName(u"nodes_groupBox")
         self.verticalLayout = QVBoxLayout(self.nodes_groupBox)
@@ -77,17 +77,17 @@ def setupUi(self, MainWindow):
         self.verticalLayout.setObjectName(u"verticalLayout")
         self.splitter.addWidget(self.nodes_groupBox)
         self.main_horizontal_splitter.addWidget(self.splitter)
-        self.scripts_tab_widget = QTabWidget(self.main_horizontal_splitter)
-        self.scripts_tab_widget.setObjectName(u"scripts_tab_widget")
+        self.flows_tab_widget = QTabWidget(self.main_horizontal_splitter)
+        self.flows_tab_widget.setObjectName(u"flows_tab_widget")
         sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
         sizePolicy1.setHorizontalStretch(0)
         sizePolicy1.setVerticalStretch(0)
-        sizePolicy1.setHeightForWidth(self.scripts_tab_widget.sizePolicy().hasHeightForWidth())
-        self.scripts_tab_widget.setSizePolicy(sizePolicy1)
+        sizePolicy1.setHeightForWidth(self.flows_tab_widget.sizePolicy().hasHeightForWidth())
+        self.flows_tab_widget.setSizePolicy(sizePolicy1)
         self.tab = QWidget()
         self.tab.setObjectName(u"tab")
-        self.scripts_tab_widget.addTab(self.tab, "")
-        self.main_horizontal_splitter.addWidget(self.scripts_tab_widget)
+        self.flows_tab_widget.addTab(self.tab, "")
+        self.main_horizontal_splitter.addWidget(self.flows_tab_widget)
         self.main_vertical_splitter.addWidget(self.main_horizontal_splitter)
         self.console_placeholder_widget = QWidget(self.main_vertical_splitter)
         self.console_placeholder_widget.setObjectName(u"console_placeholder_widget")
@@ -98,7 +98,7 @@ def setupUi(self, MainWindow):
         MainWindow.setCentralWidget(self.centralWidget)
         self.menuBar = QMenuBar(MainWindow)
         self.menuBar.setObjectName(u"menuBar")
-        self.menuBar.setGeometry(QRect(0, 0, 1368, 21))
+        self.menuBar.setGeometry(QRect(0, 0, 1368, 30))
         self.menuFile = QMenu(self.menuBar)
         self.menuFile.setObjectName(u"menuFile")
         self.menuScripts = QMenu(self.menuFile)
@@ -125,9 +125,9 @@ def setupUi(self, MainWindow):
         self.menuFile.addAction(self.actionImport_Example_Nodes)
         self.menuFile.addAction(self.actionSave_Project)
         self.menuFile.addAction(self.menuScripts.menuAction())
-        self.menuScripts.addAction(self.actionNew_Script)
-        self.menuScripts.addAction(self.actionRename_Script)
-        self.menuScripts.addAction(self.actionDelete_Script)
+        self.menuScripts.addAction(self.actionNew_Flow)
+        self.menuScripts.addAction(self.actionRename_Flow)
+        self.menuScripts.addAction(self.actionDelete_Flow)
         self.menuView.addSeparator()
         self.menuView.addAction(self.menuFlow_Design_Style.menuAction())
         self.menuView.addAction(self.menuSave_Picture.menuAction())
@@ -139,7 +139,7 @@ def setupUi(self, MainWindow):
 
         self.retranslateUi(MainWindow)
 
-        self.scripts_tab_widget.setCurrentIndex(0)
+        self.flows_tab_widget.setCurrentIndex(0)
 
 
         QMetaObject.connectSlotsByName(MainWindow)
@@ -164,13 +164,13 @@ def retranslateUi(self, MainWindow):
 "The more you zoomed in, the sharper the picture.\n"
 "This will take a few seconds.", None))
 #endif // QT_CONFIG(tooltip)
-        self.actionNew_Script.setText(QCoreApplication.translate("MainWindow", u"New", None))
-        self.actionRename_Script.setText(QCoreApplication.translate("MainWindow", u"Rename", None))
-        self.actionDelete_Script.setText(QCoreApplication.translate("MainWindow", u"Delete", None))
+        self.actionNew_Flow.setText(QCoreApplication.translate("MainWindow", u"New", None))
+        self.actionRename_Flow.setText(QCoreApplication.translate("MainWindow", u"Rename", None))
+        self.actionDelete_Flow.setText(QCoreApplication.translate("MainWindow", u"Delete", None))
         self.actionImport_Example_Nodes.setText(QCoreApplication.translate("MainWindow", u"Import Example Nodes", None))
-        self.scripts_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Scripts", None))
+        self.flows_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Flows", None))
         self.nodes_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Nodes", None))
-        self.scripts_tab_widget.setTabText(self.scripts_tab_widget.indexOf(self.tab), QCoreApplication.translate("MainWindow", u"Main", None))
+        self.flows_tab_widget.setTabText(self.flows_tab_widget.indexOf(self.tab), QCoreApplication.translate("MainWindow", u"Main", None))
         self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
         self.menuScripts.setTitle(QCoreApplication.translate("MainWindow", u"Scripts", None))
         self.menuView.setTitle(QCoreApplication.translate("MainWindow", u"View", None))
diff --git a/ryven/gui/uic/w_ui_script.py b/ryven/gui/uic/w_ui_script.py
deleted file mode 100644
index ce711ab5..00000000
--- a/ryven/gui/uic/w_ui_script.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from qtpy.QtWidgets import QWidget
-
-from ryven.gui.uic.ui_script import Ui_script_widget
-
-
-class WUIScript(QWidget):
-
-    def __init__(self):
-        super(WUIScript, self).__init__()
-        self.ui = Ui_script_widget()
-        self.ui.setupUi(self)
diff --git a/ryven/gui_env.py b/ryven/gui_env.py
new file mode 100644
index 00000000..c31166c7
--- /dev/null
+++ b/ryven/gui_env.py
@@ -0,0 +1,2 @@
+# re-exporting the gui_env module from ryven.main.packages.gui_env
+from ryven.main.packages.gui_env import *
diff --git a/ryven/main/Ryven.py b/ryven/main/Ryven.py
index dd0113b2..ac9ad27d 100644
--- a/ryven/main/Ryven.py
+++ b/ryven/main/Ryven.py
@@ -1,105 +1,217 @@
 import os
 import sys
-import argparse
 
-from ryven.main.utils import abs_path_from_package_dir
-from ryven.NENV import init_node_env
-from ryven.NWENV import init_node_widget_env
-
-# # change directory to this file's location
-# os.chdir(os.path.dirname(os.path.realpath(__file__)))
-
-# turn off for debugging
-REDIRECT_CONSOLE_OUTPUT = True
-
-
-def run(qt_app=None, qt_api='pyside2',
-        show_dialog=True, gui_parent=None, window_title='Ryven',
-        window_theme_name='dark', flow_theme=None,
-        redirect_console_output=REDIRECT_CONSOLE_OUTPUT):
+import ryven.main.packages.nodes_package
+from ryven.main import utils
+from ryven.main.config import Config
+from ryven.node_env import init_node_env
+from ryven.gui_env import init_node_guis_env
+from ryven.main.args_parser import process_args
+
+
+def run(*args_,
+        qt_app=None, gui_parent=None, use_sysargs=True,
+        **kwargs):
+    """Start the Ryven window.
+
+    The `*args_` and `**kwargs` arguments correspond to their positional and
+    optional command line equivalents, respectively (see `parse_args()`).
+    Optional keyword arguments are specified without the leading double hyphens
+    '--'. As a name, the corresponding 'dest' value of `add_argument` has to
+    be used. E.g. the command line:
+        ryven --window-theme=light --nodes=std --nodes=linalg myproject.json
+    becomes:
+        run('myproject.json', window_theme='light', nodes=['std', 'linalg'])
+
+    Note 1
+    ------
+    The `*args_` and `**kwargs` takes predecence and overwrites the values
+    specified on the command line (or the default values, if
+    `use_sysargs=False` is given). The exception are lists, which are appended,
+    e.g. in the example from above, the two nodes 'std' and 'linalg' are added
+    at the end of the nodes supplied at the command line.
+
+    Note 2
+    ------
+    The positional command line argument to specify the project file also
+    checks `utils.ryven_dir_path()/saves`, if it can find the project file.
+    The `*args_` does not perform this check. This is up to the developer
+    calling `run()`. The developer can always use `utils.find_project()` to
+    find projects in this additional directory.
+
+    Parameters
+    ----------
+    qt_app : QApplication, optional
+        The `QApplication` to be used. If `None` a `QApplication` is generated.
+        The default is `None`.
+    gui_parent : QWidget, optional
+        The parent `QWidget`.
+        The default is `None`.
+    use_sysargs : bool, optional
+        Whether the command line arguments should be used.
+        The default is `True`.
+    *args_ : str
+        Corresponding to the positional command line argument(s).
+    **kwargs : any
+        Corresponding to the keyword command line arguments.
+
+    Raises
+    ------
+    TypeError
+        Raised, if keyword argument is not specified by the argument parser or
+        the wrong number of positional arguments are specified.
+
+    Returns
+    -------
+    None|Main Window
+    """
+
+    # Process command line and method's arguments
+    conf: Config = process_args(use_sysargs, *args_, **kwargs)
+
+    #
+    # Qt application setup
+    #
 
     # QtPy API
-    os.environ['QT_API'] = qt_api
+    os.environ['QT_API'] = conf.qt_api
 
-    # init environment
+    # Init environment
     os.environ['RYVEN_MODE'] = 'gui'
     init_node_env()
-    init_node_widget_env()
+    init_node_guis_env()
 
-    # import gui sources
+    # Import GUI sources (must come after setting `os.environ['QT_API']`)
     from ryven.gui.main_console import init_main_console
-    from ryven.gui.startup_dialog.StartupDialog import StartupDialog
     from ryven.gui.main_window import MainWindow
     from ryven.gui.styling.window_theme import apply_stylesheet
 
-    # init qt application
+    # Init Qt application
     if qt_app is None:
         from qtpy.QtWidgets import QApplication
-        app = QApplication(sys.argv)
+        if conf.window_geometry:
+            # Pass '--geometry' argument to Qt
+            qt_args = [sys.argv[0], '-geometry', conf.window_geometry]
+        else:
+            qt_args = [sys.argv[0]]
+        app = QApplication(qt_args)
     else:
         app = qt_app
 
-    # register fonts
+    # Register fonts
     from qtpy.QtGui import QFontDatabase
     db = QFontDatabase()
-    db.addApplicationFont(abs_path_from_package_dir('resources/fonts/poppins/Poppins-Medium.ttf'))
-    db.addApplicationFont(abs_path_from_package_dir('resources/fonts/source_code_pro/SourceCodePro-Regular.ttf'))
-    db.addApplicationFont(abs_path_from_package_dir('resources/fonts/asap/Asap-Regular.ttf'))
-
-    # startup dialog
-    window_theme = None
-    editor_init_config = {}
-    if show_dialog:
-        sw = StartupDialog(window_theme_name, gui_parent)
-        sw.exec_()
-
-        # exit if dialog couldn't initialize
-        if sw.editor_startup_configuration == {}:
-            sys.exit()
-
-        window_theme = sw.window_theme
-        editor_init_config = sw.editor_startup_configuration
-    else:
-        window_theme = apply_stylesheet(window_theme_name)
-        editor_init_config = {'action': None}
-
-    # adjust flow theme if not set
-    if flow_theme is None:
-        flow_theme = 'pure dark' if window_theme.name == 'dark' else 'pure light'
-
-    # init main console
-    console_stdout_redirect, \
-    console_errout_redirect = init_main_console(window_theme)
+    db.addApplicationFont(
+        utils.abs_path_from_package_dir('resources/fonts/poppins/Poppins-Medium.ttf'))
+    db.addApplicationFont(
+        utils.abs_path_from_package_dir('resources/fonts/source_code_pro/SourceCodePro-Regular.ttf'))
+    db.addApplicationFont(
+        utils.abs_path_from_package_dir('resources/fonts/asap/Asap-Regular.ttf'))
+
+    #
+    # Editor configuration
+    #
+
+    # editor_config = {}
+
+    # Startup dialog
+    if conf.show_dialog:
+        from ryven.gui.startup_dialog.StartupDialog import StartupDialog
+
+        # Get packages and project file interactively and update arguments accordingly
+
+        sw = StartupDialog(config=conf, parent=gui_parent)
+        # Exit if dialog couldn't initialize or is exited
+        if sw.exec_() <= 0:
+            sys.exit('Start-up screen dismissed')
+
+    # Replace node directories with `NodePackage` instances
+    if conf.nodes:
+        conf.nodes, pkgs_not_found, _ = ryven.main.packages.nodes_package.process_nodes_packages(list(conf.nodes))
+        if pkgs_not_found:
+            sys.exit(
+                f'Error: Nodes packages not found: {", ".join([str(p) for p in pkgs_not_found])}')
+
+        # editor_config['requested packages'] = conf.nodes
+
+    # Store WindowTheme object
+    conf.window_theme = apply_stylesheet(conf.window_theme)
+
+    # Adjust flow theme if not set
+    if conf.flow_theme is None:
+        if conf.window_theme.name == 'dark':
+            conf.flow_theme = 'pure dark'
+        else:
+            conf.flow_theme = 'pure light'
+
+    # Note:
+    #   At this point, the Config is fully populated with exact types
+    #   (e.g. `conf.nodes` is a list of `NodePackage` instances, and
+    #   not a list of `str` anymore).
+
+    # Get packages required by the project
+    if conf.project:
+        pkgs, pkgs_not_found, project_dict = ryven.main.packages.nodes_package.process_nodes_packages(
+            conf.project, requested_packages=list(conf.nodes))
+
+        if pkgs_not_found:
+            str_missing_pkgs = ', '.join([str(p.name) for p in pkgs_not_found])
+            plural = len(pkgs_not_found) > 1
+            sys.exit(
+                f'The package{"s" if plural else ""} {str_missing_pkgs}'
+                f'{"were" if plural else "was"} requested, '
+                f'but {"they are" if plural else "it is"} not available.\n'
+                f'Update the project file or supply the missing package{"s" if plural else ""} '
+                f'{str_missing_pkgs} on the command line with the "-n" switch.')
+
+        requested_packages = conf.nodes
+        required_packages = pkgs
+        project_content = project_dict
 
-    # init main window
-    editor = MainWindow(editor_init_config, window_title, window_theme, flow_theme, parent=gui_parent)
+    else:
+        requested_packages = conf.nodes
+        required_packages = None
+        project_content = None
+
+    #
+    # Launch editor
+    #
+
+    # Init main console
+    (console_stdout_redirect, console_errout_redirect) = \
+        init_main_console(conf.window_theme)
+
+    # Init main window
+    editor = MainWindow(
+        config=conf,
+        requested_packages=requested_packages,
+        required_packages=required_packages,
+        project_content=project_content,
+        parent=gui_parent
+    )
     editor.show()
 
+    # Start application
     if qt_app is None:
-        if redirect_console_output:  # redirect console output
-            from contextlib import redirect_stdout, redirect_stderr
+        if conf.verbose:
+            # Run application
+            editor.print_info()
+            sys.exit(app.exec_())
 
-            with redirect_stdout(console_stdout_redirect), \
-                    redirect_stderr(console_errout_redirect):
+        else:
+            # Redirect console output
+            import contextlib
 
-                # run
+            with contextlib.redirect_stdout(console_stdout_redirect), \
+                    contextlib.redirect_stderr(console_errout_redirect):
+                # Run application
                 editor.print_info()
                 sys.exit(app.exec_())
-        else:
-            # run
-            editor.print_info()
-            sys.exit(app.exec_())
+
     else:
         return editor
 
 
 if __name__ == '__main__':
 
-    # # parse arguments
-    # parser = argparse.ArgumentParser(prog='ryven', description='Configuration options for Ryven')
-    # parser.add_argument('--no-dialog', action='store_true',
-    #                     help='if you want to skip the intro dialog; creates a new project')
-    # args = parser.parse_args()
-    # print(args.no_dialog)
-
     run()
diff --git a/ryven/main/RyvenConsole.py b/ryven/main/RyvenConsole.py
index 495eff49..f3aa935a 100644
--- a/ryven/main/RyvenConsole.py
+++ b/ryven/main/RyvenConsole.py
@@ -1,136 +1,136 @@
 """
 This module includes the whole Ryven Console application.
-It's quite short as all functionality is nicely split between backend and frontend
-so here we can just load a project into the backend and provide the session.
+It simply deploys a session with the project provided and implements a REPL.
+This is supposed to be used in an interactive way. For scripting and deploying
+projects without GUI programmatically, the ryvencore lib should be used directly.
 """
-
+import argparse
 import os
 from os.path import join, dirname
 import json
 import sys
+import code
 
 from ryvencore import *
 
-from ryven.main.nodes_package import NodesPackage
-from ryven.main.utils import import_nodes_package
-from ryven.NENV import init_node_env
+# import ryven utils to load node packages and parse projects
+from ryven.main.packages.nodes_package  import NodesPackage, process_nodes_packages, import_nodes_package
+from ryven.main.packages.node_env       import init_node_env
+from ryven.main.utils                   import find_project
 
 
-cmds = [
+def parse_args() -> argparse.Namespace:
+    """Parse the command line arguments.
 
-]
+    :return: args: The parsed command line arguments.
+    """
 
+    parser = argparse.ArgumentParser(
+        description='''
+            Console REPL for interactively running a ryven project 
+            without any GUI components. This should now require any
+            dependencies.
+            ''',
+    )
 
-class ContextContainer:
-    pass
+    parser.add_argument(
+        nargs=1,
+        dest='project',
+        metavar='PROJECT',
+        help='the project file to be loaded'
+    )
 
+    parser.add_argument(
+        '-n', '--nodes',
+        action='append',
+        # nargs='+', action='extend',
+        default=[],
+        dest='nodes',
+        metavar='NODES_PKG',
+        help='''
+            load a nodes package\\
+            • If you want to load multiple packages, use the option again.\\
+            • Nodes packages loaded here take precedence over nodes packages
+            with the same name specified in the project file!\\
+            • Nodes package names containing spaces must be enclosed in quotes.
+            '''
+    )
 
-def next_input(msg: str = ''):
+    args, remaining_args = parser.parse_known_args()
 
-    if 'test' in sys.argv and len(cmds) > 0:
-        if msg != '':
-            print(msg)
+    return args
 
-        cmd = cmds[0]
-        cmds.remove(cmd)
-    else:
-        cmd = input(msg)
 
-    return cmd
+def repl(session: Session):
+    context = {'session': session}
+    
+    code.interact(local=context, banner=
+                  f'Welcome to the Ryven Console! Your project has been loaded.\n'
+                  f'You can access the ryvencore session by typing `session`.\n'
+                  f'For more information, visit https://leon-thomm.github.io/ryvencore/\n'
+                  f'\n\n')
 
 
 def run():
 
-    os.environ['RYVEN_MODE'] = 'no-gui'
-    init_node_env()
-
-    # CLASSES['node base'] = NodeBaseWrapper
-    session = Session(gui=False)
-    session.register_nodes(
-        import_nodes_package(
-            NodesPackage(
-                directory=join(dirname(__file__), 'nodes/built_in/')
-            )
-        )
-    )
+    args = parse_args()
 
-    c = ContextContainer()
-    setattr(c, 'session', session)
+    #
+    # initialize session
+    #
 
-    print(
-        """COMMANDS:
->>> import nodes
->>> load project
-anything else will be evaluated/executed
-built-in nodes are already imported
-        """
-    )
-
-    try:
+    os.environ['RYVEN_MODE'] = 'no-gui'
+    init_node_env()
 
-        while True:
+    # import built-in nodes
+    nodes, data_types = import_nodes_package(NodesPackage(
+        directory=join(dirname(__file__), 'packages/built_in/')
+    ))
 
-            # process input commands
+    # init session
+    session = Session(gui=False, load_addons=True)
+    session.register_data_types(data_types)
+    session.register_node_types(nodes)
 
-            cmd = next_input()
+    # 
+    # load project and node packages
+    # 
 
-            # special commands
-            if cmd == 'import nodes':
-                import_nodes(session, c)
-                continue
-            elif cmd == 'load project':
-                load_project(session, c)
-                continue
+    project_path = args.project[0]
+    node_packages, nodes_not_found, project = process_nodes_packages(
+        project_or_nodes=project_path, 
+        requested_packages=args.nodes
+    )
 
-            # REPL
-            locals_ = c.__dict__
+    if nodes_not_found:
+        exit_missing_nodes(nodes_not_found)
 
-            try:
-                try:
-                    print(eval(cmd, globals(), locals_))
+    for np in node_packages:
+        nodes, data_types = import_nodes_package(package=np)
+        session.register_data_types(data_types)
+        session.register_node_types(nodes)
+    
+    session.load(project)
 
-                except SyntaxError:
-                    out = exec(cmd, globals(), locals_)
+    #
+    # deploy REPL
+    #
 
-                    if out is not None:
-                        print(out)
-            except Exception as e:
-                print(e)
-                continue
+    repl(session)
 
-            # merge locals
-            for name, val in locals_.items():
-                setattr(c, name, val)
 
-    except:
-        sys.exit('exiting...')
+def exit_missing_nodes(nodes_not_found):
+    mul = len(nodes_not_found) > 1  # multiple packages missing ?
+    sys.exit(
+        f'The package{"s" if mul else ""} '
+        f''''{"', '".join([str(p) for p in nodes_not_found])}' '''
+        f'{"were" if mul else "was"} requested, '
+        f'but {"they are" if mul else "it is"} not available.'
+        f'\n'
+        f'Update the project file or supply the missing package{"s" if mul else ""} '
+        f''''{"', '".join([p.name for p in nodes_not_found])}' '''
+        f'on the command line with the "-n" switch.')
 
 
 if __name__ == '__main__':
     run()
-
-
-# specific commands
-
-def import_nodes(session, context_container):
-    pkg_dir = next_input('abs path to your package dir: ')
-    try:
-        # package_name = os.path.basename(pkg_dir)
-        nodes = import_nodes_package(package=NodesPackage(pkg_dir))
-        session.register_nodes(nodes)
-        print('registered nodes: ', nodes)
-    except Exception as e:
-        print(e)
-
-
-def load_project(session, context_container):
-    project_file_path = next_input('abs path to your project file: ')
-    try:
-        f = open(project_file_path, 'r')
-        project = json.loads(f.read())
-        f.close()
-        del f
-        scripts = session.load(project)
-        print(f'added scripts: {scripts}')
-    except Exception as e:
-        print(e)
diff --git a/ryven/main/args_parser.py b/ryven/main/args_parser.py
new file mode 100644
index 00000000..7ba5861a
--- /dev/null
+++ b/ryven/main/args_parser.py
@@ -0,0 +1,533 @@
+import argparse
+import pathlib
+import sys
+
+from ryven.main.utils import find_config_file
+
+from ryven.main.utils import ryven_version
+from ryven.main import utils
+from ryven.main.config import Config
+
+
+class CustomHelpFormatter(argparse.HelpFormatter):
+
+    def _split_lines(self, text, width):
+        text = self._whitespace_matcher.sub(' ', text).strip()
+        # The textwrap module is used only for formatting help.
+        # Delay its import for speeding up the common usage of argparse.
+        import textwrap
+        r = []
+        for t in text.split('\\'):
+            r.extend(textwrap.wrap(t.strip(), width))
+        return r
+
+    def _fill_text(self, text, width, indent):
+        text = self._whitespace_matcher.sub(' ', text).strip()
+        import textwrap
+        return '\n'.join([
+            textwrap.fill(
+                t.strip(), width,
+                initial_indent=indent, subsequent_indent=indent)
+            for t in text.split('\\')])
+
+
+class CustomArgumentParser(argparse.ArgumentParser):
+    """An `ArgumentParser` for 'key: value' configuration files.
+
+    Configuration files can be specified on the command line with a prefixed
+    at-sign '@'. Later command line arguments (or additional configuration
+    files) override previous values.
+
+    The format of the configuration files is 'key[:value]' and very similar to
+    the long command line arguments, e.g.
+        - '--example=basics' (or '--example basics') on the command line
+          becomes 'example: basics' (or 'example=basics') in the configuration
+          file.
+        - '--verbose' on the command line simply becomes 'verbose' in the
+          configuration file.
+
+    Some more comments on the file format:
+        - The key is always the long format of the command line argument to be
+          set.
+        - Spaces before the key are allowed.
+        - Spaces after the value are stripped.
+        - Spaces are allowed on either side of ':' (or '=').
+        - Empty lines are allowed.
+        - Comments can be added after the hash sign '#'; either on a line on
+          its own or as inline comments after 'key[:value]'.
+
+    https://tricksntweaks.blogspot.com/2013_05_01_archive.html
+    """
+
+    # Regular expression to split on spaces except within quotes - returns a
+    # tuple with three values:
+    #     (<inside double quotes>, < inside single quotes>, <without quotes>)
+    value_re = r'''"([^"]*)"|'([^']*)'|([\S]+)'''
+
+    def __init__(self, *args, **kwargs):
+        # Inject `fromfile_prefix_char='@'` to the keyword arguments
+        kwargs['fromfile_prefix_chars'] = '@'
+        super().__init__(*args, **kwargs)
+
+    def convert_arg_line_to_args(self, line):
+        """
+        Convert 'key: value' lines to the long command line arguments.
+
+        The following line formats are allowed:
+            - 'key': A simple switch; becomes '--key'.
+            - 'key: value': An argument with a value; becomes '--key=value'.
+            - 'key=value': Equivalent to 'key: value'; becomes '--key=value'.
+            - 'key value': Equivalent to 'key: value'; becomes '--key value'.
+            - Quotes around values are removed.
+            - Comments behind '#' are removed
+            - Empty lines are removed
+
+        See:
+            https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args
+
+        Parameters
+        ----------
+        line : str
+            One complete line from the configuration line.
+
+        Returns
+        -------
+        list
+            The parsed line from the configuration file with the key (including
+            the leading '--') as first element and then all values, where
+            quoted items their quotes stripped.
+
+        """
+
+        # Remove comments and white spaces around remaining line
+        args = line.split('#', maxsplit=1)[0].strip()
+        if not args:
+            # Empty line
+            return []
+
+        # Generate long command line argument
+
+        # Replace first ':' with a '='
+        args = args.replace(':', '=', 1)
+
+        # Split line into keyword and argument
+        args = [a.strip() for a in args.split('=', maxsplit=1)]
+
+        if len(args) == 1:
+            # A key without argument is a switch
+            key = args[0]
+            return [f'--{key}']
+        else:
+            key, value = args
+            # Remove optional quotes around the value
+            if value.startswith("'") and value.endswith("'"):
+                value = value[1:-1]
+            elif value.startswith('"') and value.endswith('"'):
+                value = value[1:-1]
+            # Assemble long command line
+            return [f'--{key}', value]
+
+
+def parse_sys_args(just_defaults=False) -> Config:
+    """Parse the command line arguments into a `Config` instance.
+
+    Parameters
+    ----------
+    just_defaults : bool, optional
+        Whether the command line arguments are to be parsed or just the
+        defaults are returned.
+        The default is `False`, which parses the command line arguments.
+
+    Returns
+    -------
+    args : argparse.Namespace
+        The parsed command line arguments or the default values.
+
+    """
+
+    # Get available examples
+    exampledir = utils.abs_path_from_package_dir('examples_projects')
+    examples = [e.stem for e in pathlib.Path(exampledir).glob('*.json')]
+
+    #
+    # The parser
+    #
+
+    parser = CustomArgumentParser(
+        description='''
+            Flow-based visual scripting for Python.\\
+            \\
+            See https://ryven.org/guide/ for a guide on developing nodes.
+            ''',
+        formatter_class=CustomHelpFormatter)
+
+    # Positional arguments
+
+    parser.add_argument(
+        nargs='?',
+        dest='project',
+        metavar='PROJECT',
+        help=f'''
+            the project file to be loaded (the suffix ".json" can be omitted)\\
+            • If the project file cannot be found, it is searched for under the
+            directory "{pathlib.PurePath(utils.ryven_dir_path(), "saves")}".\\
+            • use "-" for standard input.
+            ''')
+
+    # Optional arguments
+
+    parser.add_argument(
+        '-s', '--skip-dialog',
+        action='store_false',
+        dest='show_dialog',
+        help='''
+            skip the start-up dialog
+            ''')
+
+    parser.add_argument(
+        '-V', '--version',
+        action='version',
+        version=f'%(prog)s {ryven_version()}')
+
+    parser.add_argument(
+        '-v', '--verbose',
+        action='store_true',
+        help=f'''
+            prevents redirect of stderr and stdout to the in-editor console\\
+            and prints lots of debug information to the default stderr and stdout
+            ''')
+
+    parser.add_argument(
+        '--enable-code-editing',
+        action='store_true',
+        dest='src_code_edits_enabled',
+        help=f'''
+            • Enables a (highly unstable and hacky) feature that allows temporary\\ 
+            editing of the source code of nodes in the source code preview panel\\
+            (useful for debugging)\\
+            • When enabled, Ryven might consume much more memory than usual
+            ''')
+
+    # Project configuration
+
+    group = parser.add_argument_group('project configuration')
+
+    group.add_argument(
+        '-n', '--nodes',
+        action='append',
+        # nargs='+', action='extend',
+        default=Config.nodes,
+        dest='nodes',
+        metavar='NODES_PKG',
+        help='''
+            load a nodes package\\
+            • If you want to load multiple packages, use the option again.\\
+            • Packages loaded here take precedence over packages
+            with the same name specified in the project file!\\
+            • Package names containing spaces must be enclosed in quotes.
+            ''')
+
+    group.add_argument(
+        '-x', '--example',
+        choices=examples,
+        dest='example',
+        help='load an example project (do not give the PROJECT argument)')
+
+    # Display
+
+    group = parser.add_argument_group('display')
+
+    group.add_argument(
+        '-w', '--window-theme',
+        choices=Config.get_available_window_themes(),
+        default=Config.window_theme,
+        dest='window_theme',
+        help='''
+            set the window theme\\
+            Default: %(default)s
+            ''')
+
+    group.add_argument(
+        '-f', '--flow-theme',
+        choices=Config.get_available_flow_themes(),
+        dest='flow_theme',
+        help='''
+            set the theme of the flow view\\
+            • The theme's name must be put between quotation marks, if it
+            contains spaces.\\
+            Default: {pure dark|pure light}, depending on the window theme
+            ''')
+
+    group.add_argument(
+        '--performance',
+        choices=Config.get_available_performance_modes(),
+        default=Config.performance_mode,
+        dest='performance_mode',
+        help='''
+            select performance mode\\
+            Default: %(default)s
+            ''')
+
+    exclusive_group = group.add_mutually_exclusive_group()
+    exclusive_group.add_argument(
+        '--no-animations',
+        action='store_false',
+        dest='animations',
+        help=f'''
+            do not use animations\\
+            Default: {'Use' if Config.animations else 'Do not use'} animations
+            ''')
+    exclusive_group.add_argument(
+        '--animations',
+        action='store_true',
+        dest='animations',
+        help=f'''
+            use animations\\
+            Default: {'Use' if Config.animations else 'Do not use'} animations
+            ''')
+
+    group.add_argument(
+        '--geometry',
+        dest='window_geometry',
+        metavar='[WxH][{+,-}X{+,-}Y]',
+        help='''
+            change the size of the window to WxH and
+            position it at X,Y on the screen
+            ''')
+
+    group.add_argument(
+        '-t', '--title',
+        default=Config.window_title,
+        dest='window_title',
+        help='''
+            changes the window's title\\
+            Default: %(default)s
+            ''')
+
+    group.add_argument(
+        '-q', '--qt-api',
+        default=Config.qt_api,
+        dest='qt_api',
+        help='''
+            the QT API to be used\\
+            • Notice that only PySide versions are allowed, Ryven does not work with PyQt.\\
+            Default: %(default)s
+            ''')
+
+    # Configuration files
+
+    parser.add_argument_group(
+        'configuration files',
+        description=f'''
+            One or more configuration files for automatically loading optional
+            arguments can be used at any position.\\
+            • If the file
+            "{pathlib.Path(utils.ryven_dir_path()).joinpath("ryven.cfg")}"
+            exists, it will always be read as the very first configuration
+            file.\\
+            • This default configuration file is created with an example during 
+            installation.\\
+            • To explicitly load a configuration file from a given location,
+            the file name must be preceded with the @-sign, e.g. "@ryven.cfg".\\
+            • The later command line arguments or configuration files take
+            precedence over earlier specified arguments.\\
+            • The format is like the long command line argument, but with the
+            leading two hyphens removed. If the argument takes a value, this
+            comes after a colon or an equal sign, e.g. "example: basics" or
+            "example=basics".\\
+            • There is no need to enclose values containing spaces in quotes as
+            on the command line, but they can be enclosed if preferred.\\
+            • Symmetric single or double quotes around values are removed.\\
+            • Comments can be inserted after the hash sign "#" inline or on
+            a line on their own.
+            ''')
+
+    #
+    # Process the arguments
+    #
+
+    # Parse the arguments
+    if just_defaults:
+        args = parser.parse_args([], namespace=Config())
+    else:
+        args = parser.parse_args(namespace=Config())
+
+    # Check, if project file exists
+    if args.project:
+        if args.project == '-':
+            args.project = sys.stdin
+        else:
+            project = utils.find_project(args.project)
+            if project is None:
+                parser.error(
+                    'project file does not exist')
+            args.project = project
+
+    # Make a `set` of paths to node packages
+    args.nodes = set([pathlib.Path(nodes_pkg) for nodes_pkg in args.nodes])
+
+    # Put example into 'project' argument
+    if args.example:
+        if args.project:
+            parser.error(
+                'when loading an example, no argument PROJECT is allowed')
+
+        args.project = pathlib.Path(exampledir, args.example).with_suffix('.json')
+
+    return args
+
+
+def quote(s):
+    """Puts strings with spaces in quotes; strings without spaces remain unchanged"""
+
+    if ' ' in s:
+        # Values with spaces must be enclosed in quotes
+        return f'"{s}"'
+    else:
+        return s
+
+
+def unparse_sys_args(args: Config):
+    """Generate command line and configuration file.
+
+    Reverse parsing the args namespace into two strings:
+        - a command representing the command line arguments
+        - the content of the corresponding config file
+
+    Parameters
+    ----------
+    args : argparse.Namespace
+        The arguments containing the configuration, just like what
+        `parse_sys_args()` returns.
+
+    Returns
+    -------
+    command : string
+        The command line argument that would generate the supplied
+        configuration.
+    config : string
+        The contents of a config file that would generate the supplied
+        configuration.
+
+    """
+
+    cmd_line = ['ryven']
+    cfg_file = []
+
+    for key, value in vars(args).items():
+
+        key = key.replace('_', '-')
+
+        if value is True:
+            # A positive switch
+            cmd_line.append(f'--{key}')
+            cfg_file.append(key)
+        elif value is False:
+            # A negative switch
+            cmd_line.append(f'--no-{key}')
+            cfg_file.append(f'no-{key}')
+            # TODO: not all switches have a negative form; e.g. --verbose does not
+        else:
+            # An argument with a value
+
+            if value is None:
+                continue
+
+            if isinstance(value, pathlib.Path):
+                value = str(value)
+
+            value_quoted = quote(value)
+
+            if key == 'nodes':
+                for n in value:
+                    value_quoted = quote(str(n))
+
+                    cmd_line.append(f'-n {value_quoted}')
+                    cfg_file.append(f'nodes: {n}')
+
+            elif key == 'project':
+                continue
+
+            else:
+                cmd_line.append(f'--{key}={value_quoted}')
+                cfg_file.append(f'{key}: {value}')
+
+    if args.project:
+        cmd_line.append(f'{quote(str(args.project))}')
+
+        # The config file should not contain a project.
+
+    return ' '.join(cmd_line), '\n'.join(cfg_file)
+
+
+def process_args(use_sysargs, *args_, **kwargs) -> Config:
+    """Completely processes all arguments given either through sys args,
+    or through function arguments. Performs checks on argument correctness,
+    and injects the arguments from the default config file
+
+    Parameters
+    ----------
+    use_sysargs : bool, optional
+        Whether the command line arguments should be used.
+        The default is `True`.
+    *args_
+        Corresponding to the positional command line argument(s).
+    **kwargs
+        Corresponding to the keyword command line arguments.
+
+    Returns
+    -------
+    args : argparse.Namespace
+        The parsed command line arguments or the default values, merged with
+        config file arguments.
+    """
+
+    # Inject default configuration file in user's directory as first argument
+    config_file = pathlib.Path(utils.ryven_dir_path()).joinpath('ryven.cfg')
+    if config_file.exists():
+        sys.argv.insert(1, f'@{config_file}')
+
+    # Locate config files
+    if use_sysargs:
+        for val in sys.argv:
+            if val.startswith('@'):
+                i = sys.argv.index(val)
+                sys.argv.remove(val)
+                path = str(find_config_file(val.strip('@')))
+                if path is None:
+                    sys.exit(f"Error: could not find config file: {val}")
+                sys.argv.insert(i, '@'+path)
+
+        # Get parsed command line arguments
+        args = parse_sys_args()
+    else:
+        # Get default values
+        args = parse_sys_args(just_defaults=True)
+
+    # Update command line arguments with keyword arguments to run()
+    for key, value in kwargs.items():
+        if not hasattr(args, key):
+            raise TypeError(
+                f"run() got an unexpected keyword argument '{key}'")
+        if isinstance(getattr(args, key), list):
+            getattr(args, key).extend(value)
+        elif isinstance(getattr(args, key), set):
+            setattr(args, key, getattr(args, key).union(set(value)))
+        else:
+            setattr(args, key, value)
+
+    # Check project
+    if len(args_) > 1:                 # Just one argument, but more given
+        raise TypeError(
+            f'run() takes 1 positional argument, but {len(args_)} were given')
+    elif args_:                          # Exactly one argument given
+        # Update the 'project' argument with the positional arguments to run()
+        # Note, this is intentionally generic, so that changes in `parse_args`
+        # does not require changes here!
+        project = utils.find_project(args_[0])
+        if project is None:
+            raise IOError(f'project "{args_[0]}" not found')
+        else:
+            args.project = project
+
+    return args
diff --git a/ryven/main/config.py b/ryven/main/config.py
new file mode 100644
index 00000000..77d978ce
--- /dev/null
+++ b/ryven/main/config.py
@@ -0,0 +1,65 @@
+import pathlib
+from typing import Optional, Literal, List, Dict, Set, Union
+
+from ryven import NodesPackage
+from ryven.gui.styling.window_theme import WindowTheme
+
+
+class Config:
+    """
+    Internal representation of the configuration that the application is
+    currently using.
+
+    The args parser uses the default values of this class for the defaults
+    of config arguments (or cmd line). At runtime, this class is
+    instantiated and populated with the values of the config arguments.
+
+    WHEN MODIFYING THIS CLASS, make sure to update parse_sys_args() in
+    args_parser.py accordingly.
+    """
+
+    #
+    # config options declaration with default values.
+    # the options where the type is a union will be literals
+    # initially, but will be converted to actual types
+    # by the args parser.
+    #
+
+    project: Optional[pathlib.Path] = None
+    show_dialog: bool = True
+    verbose: bool = False
+    nodes: Union[Set[pathlib.Path], Set[NodesPackage]] = []
+    example: Optional[str] = None
+    window_theme: Union[str, WindowTheme] = 'dark'
+    flow_theme: Optional[str] = None  # None means it depends on window_theme
+    performance_mode: str = 'pretty'
+    animations: bool = True
+    window_geometry: Optional[str] = None
+    window_title: str = 'Ryven'
+    qt_api: str = 'pyside2'
+    src_code_edits_enabled: bool = False
+
+    @staticmethod
+    def get_available_window_themes() -> Set[str]:
+        return {'dark', 'light', 'plain'}
+
+    @staticmethod
+    def get_available_flow_themes() -> Set[str]:
+        # FIXME: this is not stable api; expose it properly in ryvencore-qt
+        from ryvencore_qt.src.Design import Design
+        return {t.name for t in Design().flow_themes}
+
+    @staticmethod
+    def get_available_performance_modes() -> Set[str]:
+        return {'pretty', 'fast'}
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        global instance
+        if instance is not None:
+            raise RuntimeError('Config is a singleton')
+        instance = self
+
+
+instance: Optional[Config] = None
diff --git a/ryven/main/nodes/NodeBase.py b/ryven/main/nodes/NodeBase.py
deleted file mode 100644
index ff33751f..00000000
--- a/ryven/main/nodes/NodeBase.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from ryvencore_qt import Node as RC_Node
-
-
-class NodeBase(RC_Node):
-
-    # CONSOLE
-
-    main_console = None
-
-    # CODE
-
-    __class_codes__: dict = None  # used by the src code preview
-
-    # def __init__(self, params):
-    #     super().__init__(params)
-    #
-    #     self.__obj_codes__ = {}  # gets set by src code preview
-
-
-    def init_default_actions(self) -> dict:
-        actions = super().init_default_actions()
-        actions['console ref'] = {'method': self.add_to_console}
-        return actions
-
-    def add_to_console(self):
-        # from MainConsole import MainConsole
-        # MainConsole.instance.add_obj_context(self)
-        self.main_console.add_obj_context(self)
diff --git a/ryven/main/nodes/built_in/nodes.py b/ryven/main/nodes/built_in/nodes.py
deleted file mode 100644
index 33cdf62b..00000000
--- a/ryven/main/nodes/built_in/nodes.py
+++ /dev/null
@@ -1,282 +0,0 @@
-from ryven.NENV import *
-widgets = import_widgets(__file__)
-
-
-class NodeBase(Node):
-    pass
-
-
-class GetVar_Node(NodeBase):
-    """Gets the value of a script variable"""
-
-    version = 'v0.1'
-
-    title = 'get var'
-    init_inputs = [
-        NodeInputBP(dtype=dtypes.String(size='m')),
-    ]
-    init_outputs = [
-        NodeOutputBP(label='val')
-    ]
-    color = '#c69a15'
-
-    def __init__(self, params):
-        super().__init__(params)
-
-        self.var_name = ''
-        self.temp_var_val = None
-
-    def place_event(self):
-        self.update()
-
-    def view_place_event(self):
-        self.var_name = self.input(0)
-
-    def update_event(self, input_called=-1):
-        if self.input(0) != self.var_name:
-
-            if self.var_name != '':  # disconnect old var val update connection
-                self.unregister_var_receiver(self.var_name, self.var_val_changed)
-
-            self.var_name = self.input(0)
-
-            # create new var update connection
-            self.register_var_receiver(self.var_name, self.var_val_changed)
-
-        self.set_output_val(0, self.get_var_val(self.var_name))
-
-    def var_val_changed(self, name, val):
-        self.set_output_val(0, val)
-
-
-class Result_Node(NodeBase):
-    """Simply shows a value converted to str"""
-
-    version = 'v0.1'
-
-    title = 'result'
-    init_inputs = [
-        NodeInputBP(type_='data'),
-    ]
-    main_widget_class = widgets.Result_Node_MainWidget
-    main_widget_pos = 'between ports'
-    color = '#c69a15'
-
-    def __init__(self, params):
-        super().__init__(params)
-        self.val = None
-
-    def place_event(self):
-        self.update()
-
-    def view_place_event(self):
-        self.main_widget().show_val(self.val)
-
-    def update_event(self, input_called=-1):
-        self.val = self.input(0)
-        if self.session.gui:
-            self.main_widget().show_val(self.val)
-
-
-class Val_Node(NodeBase):
-    """Evaluates a string from the input field"""
-
-    version = 'v0.1'
-
-    title = 'val'
-    init_inputs = [
-        NodeInputBP(dtype=dtypes.Data(size='s')),
-    ]
-    init_outputs = [
-        NodeInputBP(type_='data'),
-    ]
-    style = 'small'
-    color = '#c69a15'
-
-    def __init__(self, params):
-        super().__init__(params)
-
-        self.display_title = ''
-        self.actions['edit val via dialog'] = {'method': self.action_edit_via_dialog}
-        self.val = None
-
-
-    def place_event(self):
-        self.update()
-
-    def update_event(self, input_called=-1):
-        self.val = self.input(0)
-        self.set_output_val(0, self.val)
-
-    def action_edit_via_dialog(self):
-        return
-
-        # from ..EditVal_Dialog import EditVal_Dialog
-        #
-        # val_dialog = EditVal_Dialog(parent=None, init_val=self.val)
-        # accepted = val_dialog.exec_()
-        # if accepted:
-        #     self.main_widget().setText(str(val_dialog.get_val()))
-        #     self.update()
-
-    def get_current_var_name(self):
-        return self.input(0)
-
-    def get_state(self):
-        return {
-            'val': self.val  # self.main_widget().get_val()
-        }
-
-    def set_state(self, data, version):
-        self.val = data['val']
-
-        if version is None:
-
-            self.display_title = ''
-
-            self.create_input_dt(dtype=dtypes.Data(size='s'))
-
-            # the old version didn't use a dtype
-            self.inputs[0].dtype.val = self.val
-            self.inputs[0].update(self.val)
-
-
-
-class SetVar_Node(NodeBase):
-    """Sets the value of a script variable"""
-
-    version = 'v0.1'
-
-    title = 'set var'
-    init_inputs = [
-        NodeInputBP(type_='exec'),
-        NodeInputBP(dtype=dtypes.String(), label='var'),
-        NodeInputBP(dtype=dtypes.Data(size='s'), label='val'),
-    ]
-    init_outputs = [
-        NodeOutputBP(type_='exec'),
-        NodeOutputBP(type_='data', label='val'),
-    ]
-    style = 'normal'
-    color = '#c69a15'
-
-    def __init__(self, params):
-        super().__init__(params)
-
-        self.actions['make passive'] = {'method': self.action_make_passive}
-        self.active = True
-
-        self.var_name = ''
-        self.num_vars = 1
-
-    def update_event(self, input_called=-1):
-
-        if self.active and input_called == 0:
-
-            if self.set_var_val(self.input(1), self.input(2)):
-                self.set_output_val(1, self.input(2))
-            self.exec_output(0)
-
-        elif not self.active:
-
-            self.var_name = self.input(0)
-            if self.set_var_val(self.input(0), self.input(1)):
-                self.set_output_val(0, self.get_var_val(self.var_name))
-
-    def action_make_passive(self):
-        self.active = False
-        self.delete_input(0)
-        self.delete_output(0)
-        del self.actions['make passive']
-        self.actions['make active'] = {'method': self.action_make_active}
-
-    def action_make_active(self):
-        self.active = True
-        self.create_input(type_='exec', insert=0)
-        self.create_output(type_='exec', insert=0)
-        del self.actions['make active']
-        self.actions['make passive'] = {'method': self.action_make_passive}
-
-    def get_state(self):
-        return {'active': self.active}
-
-    def set_state(self, data, version):
-        self.active = data['active']
-
-
-class SetVarsPassive_Node(NodeBase):
-    """Sets the values of multiple script variables"""
-
-    version = 'v0.1'
-
-    title = 'set vars passive'
-    init_inputs = []
-    init_outputs = []
-    style = 'normal'
-    color = '#c69a15'
-
-    def __init__(self, params):
-        super().__init__(params)
-
-        self.actions['add var input'] = {'method': self.add_var_input}
-
-        self.num_vars = 0
-
-    def place_event(self):
-        if self.num_vars == 0:
-            self.add_var_input()
-
-    def add_var_input(self):
-        self.create_input_dt(label='var', dtype=dtypes.String(size='l'))
-        self.create_input_dt(label='val', dtype=dtypes.Data(size='l'))
-        self.num_vars += 1
-
-        self.actions[f'remove var {self.num_vars}'] = {
-            'method': self.remove_var_input,
-            'data': self.num_vars
-        }
-
-    def remove_var_input(self, number):
-        self.delete_input((number-1)*2)
-        self.delete_input((number-1)*2)
-        self.num_vars -= 1
-        self.rebuild_remove_actions()
-
-    def rebuild_remove_actions(self):
-
-        remove_keys = []
-        for k, v in self.actions.items():
-            if k.startswith('remove var'):
-                remove_keys.append(k)
-
-        for k in remove_keys:
-            del self.actions[k]
-
-        for i in range(self.num_vars):
-            self.actions[f'remove var {i+1}'] = {
-                'method': self.remove_var_input,
-                'data': i+1
-            }
-
-    def update_event(self, input_called=-1):
-
-        var_names = [self.input(i) for i in range(0, len(self.inputs), 2)]
-        values = [self.input(i) for i in range(1, len(self.inputs), 2)]
-
-        for i in range(len(var_names)):
-            self.set_var_val(var_names[i], values[i])
-
-    def get_state(self):
-        return {'num vars': self.num_vars}
-
-    def set_state(self, data, version):
-        self.num_vars = data['num vars']
-
-
-export_nodes(
-    SetVar_Node,
-    GetVar_Node,
-    Val_Node,
-    Result_Node,
-    SetVarsPassive_Node,
-)
diff --git a/ryven/main/nodes/built_in/widgets.py b/ryven/main/nodes/built_in/widgets.py
deleted file mode 100644
index 3ed5b22d..00000000
--- a/ryven/main/nodes/built_in/widgets.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from ryven.NWENV import *
-
-from qtpy.QtCore import Signal
-from qtpy.QtWidgets import QLineEdit
-
-
-class Result_Node_MainWidget(MWB, QLineEdit):
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QLineEdit.__init__(self)
-
-        self.setReadOnly(True)
-        self.setFixedWidth(120)
-
-
-    def show_val(self, new_val):
-        self.setText(str(new_val))
-        self.setCursorPosition(0)
-
-
-class ValNode_MainWidget(MWB, QLineEdit):
-
-    value_changed = Signal(object)
-
-    def __init__(self, params):
-        MWB.__init__(self, params)
-        QLineEdit.__init__(self)
-
-        # self.setFixedWidth(80)
-        # self.setMinimumWidth(80)
-        self.resize(120, 31)
-        self.editingFinished.connect(self.editing_finished)
-
-    def editing_finished(self):
-        # self.node.update()
-        self.value_changed.emit(self.get_val())
-
-    def get_val(self):
-        val = None
-        try:
-            val = eval(self.text())
-        except Exception as e:
-            val = self.text()
-        return val
-
-    def get_state(self):
-        data = {'text': self.text()}
-        return data
-
-    def set_state(self, data):
-        self.setText(data['text'])
-
-
-export_widgets(
-    Result_Node_MainWidget,
-    ValNode_MainWidget,
-)
diff --git a/ryven/main/nodes_package.py b/ryven/main/nodes_package.py
deleted file mode 100644
index aed62432..00000000
--- a/ryven/main/nodes_package.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from os.path import basename, dirname, splitext, normpath, join
-
-
-class NodesPackage:
-    """
-    A small container to store meta data about imported node packages.
-    """
-
-    def __init__(self, directory: str):
-
-        self.name = basename(normpath(directory))
-        self.directory = directory
-
-        self.file_path = normpath(join(directory, 'nodes.py'))
-
-    def config_data(self):
-        return {
-            'name': self.name,
-            'dir': self.directory,
-        }
diff --git a/ryven/main/nodes/__init__.py b/ryven/main/packages/__init__.py
similarity index 100%
rename from ryven/main/nodes/__init__.py
rename to ryven/main/packages/__init__.py
diff --git a/ryven/main/nodes/built_in/__init__.py b/ryven/main/packages/built_in/__init__.py
similarity index 100%
rename from ryven/main/nodes/built_in/__init__.py
rename to ryven/main/packages/built_in/__init__.py
diff --git a/ryven/main/packages/built_in/gui.py b/ryven/main/packages/built_in/gui.py
new file mode 100644
index 00000000..f72ffee6
--- /dev/null
+++ b/ryven/main/packages/built_in/gui.py
@@ -0,0 +1,201 @@
+from ryvencore import Data
+
+from ryven.gui_env import *
+
+from qtpy.QtGui import QKeySequence
+from qtpy.QtCore import Signal
+from qtpy.QtWidgets import QLineEdit, QDialog, QDialogButtonBox, QMessageBox, QPlainTextEdit, QShortcut, QVBoxLayout
+
+
+class Result_Node_MainWidget(NodeMainWidget, QLineEdit):
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QLineEdit.__init__(self)
+
+        self.setReadOnly(True)
+        self.setFixedWidth(120)
+
+
+    def show_val(self, new_val):
+        self.setText(str(new_val))
+        self.setCursorPosition(0)
+
+
+class ResultGui(NodeGUI):
+    main_widget_class = Result_Node_MainWidget
+    main_widget_pos = 'between ports'
+    color = '#c69a15'
+
+
+class ValNode_MainWidget(NodeMainWidget, QLineEdit):
+
+    value_changed = Signal(object)
+
+    def __init__(self, params):
+        NodeMainWidget.__init__(self, params)
+        QLineEdit.__init__(self)
+
+        # self.setFixedWidth(80)
+        # self.setMinimumWidth(80)
+        self.resize(120, 31)
+        self.editingFinished.connect(self.editing_finished)
+
+    def editing_finished(self):
+        self.value_changed.emit(self.get_val())
+
+    def get_val(self):
+        val = None
+        try:
+            val = eval(self.text())
+        except Exception as e:
+            val = self.text()
+        return val
+
+    def get_state(self):
+        data = {'text': self.text()}
+        return data
+
+    def set_state(self, data):
+        self.setText(data['text'])
+
+
+
+class EditVal_Dialog(QDialog):
+    def __init__(self, parent, init_val):
+        super(EditVal_Dialog, self).__init__(parent)
+
+        # shortcut
+        save_shortcut = QShortcut(QKeySequence.Save, self)
+        save_shortcut.activated.connect(self.save_triggered)
+
+        main_layout = QVBoxLayout()
+
+        self.val_text_edit = QPlainTextEdit()
+        val_str = ''
+        try:
+            val_str = str(init_val)
+        except Exception as e:
+            msg_box = QMessageBox(QMessageBox.Warning, 'Value parsing failed',
+                                  'Couldn\'t stringify value', QMessageBox.Ok, self)
+            msg_box.setDefaultButton(QMessageBox.Ok)
+            msg_box.exec_()
+            self.reject()
+
+        self.val_text_edit.setPlainText(val_str)
+
+        main_layout.addWidget(self.val_text_edit)
+
+        button_box = QDialogButtonBox()
+        button_box.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
+        button_box.accepted.connect(self.accept)
+        button_box.rejected.connect(self.reject)
+
+        main_layout.addWidget(button_box)
+
+        self.setLayout(main_layout)
+        self.resize(450, 300)
+
+        self.setWindowTitle('edit val')
+
+    def save_triggered(self):
+        self.accept()
+
+    def get_val(self):
+        val = self.val_text_edit.toPlainText()
+        try:
+            val = eval(val)
+        except Exception as e:
+            pass
+        return val
+
+
+class ValGui(NodeGUI):
+    main_widget_class = ValNode_MainWidget
+    style = 'small'
+    color = '#c69a15'
+
+    def initialized(self):
+        self.main_widget().value_changed.connect(self.widget_val_updated)
+        self.actions['edit val via dialog'] = {'method': self.action_edit_via_dialog}
+
+    def widget_val_updated(self):
+        self.node.val = Data(self.main_widget().get_val())
+        self.node.update()
+
+    def action_edit_via_dialog(self):
+        val_dialog = EditVal_Dialog(parent=None, init_val=self.node.val)
+        accepted = val_dialog.exec_()
+        if accepted:
+            self.main_widget().setText(str(val_dialog.get_val()))
+            self.update()
+
+
+class SetVarsGui(NodeGUI):
+    style = 'normal'
+    color = '#c69a15'
+
+    def initialized(self):
+        self.actions['add var input'] = {'method': self.add_var}
+        self.actions['remove var input'] = {}
+        self.rebuild_remove_actions()
+
+    def add_var(self):
+        self.node.add_var_input()
+
+    def remove_var(self, i):
+        self.node.remove_var_input(i)
+
+    def rebuild_remove_actions(self):
+
+        # remove_keys = []
+        # for k, v in self.actions['remove var input'].items():
+        #     if k.startswith('remove var'):
+        #         remove_keys.append(k)
+        # for k in remove_keys:
+        #     del self.gui.actions['remove var input'][k]
+        self.actions['remove var input'] = {}
+
+        for i in range(self.node.num_vars):
+            self.actions[f'remove var input'][f'{i+1}'] = {
+                'method': self.remove_var,
+                'data': i+1
+            }
+
+
+
+class SetVarGui(NodeGUI):
+    input_widget_classes = {
+        'varname': inp_widgets.Builder.str_line_edit(),
+        'val': inp_widgets.Builder.str_line_edit(),
+    }
+    # init_input_widgets = {
+    #     1: {'name': 'varname', 'pos': 'besides'},
+    #     2: {'name': 'val', 'pos': 'besides'}
+    # }
+    style = 'normal'
+    color = '#c69a15'
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.input_widgets[self.node.inputs[-2]] = {'name': 'varname', 'pos': 'besides'}
+        self.input_widgets[self.node.inputs[-1]] = {'name': 'val', 'pos': 'besides'}
+
+
+class GetVarGui(NodeGUI):
+    input_widget_classes = {
+        'varname': inp_widgets.Builder.str_line_edit(),
+    }
+    init_input_widgets = {
+        0: {'name': 'varname', 'pos': 'besides'}
+    }
+    color = '#c69a15'
+
+
+export_guis([
+    GetVarGui,
+    ResultGui,
+    ValGui,
+    SetVarGui,
+    SetVarsGui,
+])
diff --git a/ryven/main/packages/built_in/nodes.py b/ryven/main/packages/built_in/nodes.py
new file mode 100644
index 00000000..71ace2dd
--- /dev/null
+++ b/ryven/main/packages/built_in/nodes.py
@@ -0,0 +1,269 @@
+from ryven.node_env import *
+guis = import_guis(__file__)
+import ryvencore as rc
+
+
+class NodeBase(Node):
+    def have_gui(self):
+        return hasattr(self, 'gui')
+
+    def vars_addon(self):
+        return self.get_addon('Variables')
+
+    def get_var_val(self, var_name):
+        return self.vars_addon().var(self.flow, var_name).get()
+
+    def set_var_val(self, var_name, val):
+        return self.vars_addon().var(self.flow, var_name).set(val)
+
+
+
+class GetVar_Node(NodeBase):
+    """Gets the value of a script variable"""
+
+    version = 'v0.2'
+
+    title = 'get var'
+    init_inputs = [
+        NodeInputType(),
+    ]
+    init_outputs = [
+        NodeOutputType(label='val')
+    ]
+    GUI = guis.GetVarGui
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.var_name = ''
+        self.temp_var_val = None
+
+    def place_event(self):
+        self.update()
+        if self.input(0) is not None:
+            self.var_name = self.input(0).payload
+
+    def update_event(self, input_called=-1):
+        if self.input(0).payload != self.var_name:
+
+            if self.var_name != '':  # disconnect old var val update connection
+                self.vars_addon().unsubscribe(self, self.var_name, self.var_val_changed)
+
+            self.var_name = self.input(0).payload
+
+            # create new var update connection
+            self.vars_addon().subscribe(self, self.var_name, self.var_val_changed)
+
+        self.set_output_val(0, Data(self.get_var_val(self.var_name)))
+
+    def var_val_changed(self, _):
+        self.set_output_val(0, Data(self.get_var_val(self.var_name)))
+
+
+class Result_Node(NodeBase):
+    """Simply shows a value converted to str"""
+
+    version = 'v0.2'
+
+    title = 'result'
+    init_inputs = [
+        NodeInputType(type_='data'),
+    ]
+    GUI = guis.ResultGui
+
+    def __init__(self, params):
+        super().__init__(params)
+        self.val = None
+
+    def rebuilt(self):
+        self.update()
+
+    def update_event(self, input_called=-1):
+        self.val = self.input(0).payload
+        if self.have_gui():
+            self.gui.main_widget().show_val(self.val)
+
+
+class Val_Node(NodeBase):
+    """Evaluates a string from the input field"""
+
+    version = 'v0.2'
+
+    title = 'val'
+    init_inputs = [
+        # NodeInputType(default=Data()),
+    ]
+    init_outputs = [
+        NodeInputType(type_='data'),
+    ]
+    GUI = guis.ValGui
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.display_title = ''
+        self.val = Data()
+
+
+    def place_event(self):
+        self.update()
+
+    def update_event(self, input_called=-1):
+        self.set_output_val(0, self.val)
+
+    def get_current_var_name(self):
+        return self.input(0).payload if self.input(0) is not None else None
+
+    def get_state(self):
+        return {
+            'val': self.val  # self.main_widget().get_val()
+        }
+
+    def set_state(self, data, version):
+        self.val = data['val']
+
+
+class SetVar_Node(NodeBase):
+    """Sets the value of a script variable"""
+
+    version = 'v0.1'
+
+    title = 'set var'
+    init_inputs = [
+        NodeInputType(type_='exec'),
+        NodeInputType(label='var'),
+        NodeInputType(label='val'),
+    ]
+    init_outputs = [
+        NodeOutputType(type_='exec'),
+        NodeOutputType(type_='data', label='val'),
+    ]
+
+    GUI = guis.SetVarGui
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.active = True
+
+        self.var_name = ''
+        self.num_vars = 1
+
+    def place_event(self):
+        if self.have_gui():
+            self.gui.actions['make passive'] = {'method': self.action_make_passive}
+
+    def update_event(self, input_called=-1):
+
+        if self.active and input_called == 0:
+
+            if self.set_var_val(self.input(1).payload, self.input(2).payload):
+                self.set_output_val(1, self.input(2))
+            self.exec_output(0)
+
+        elif not self.active:
+
+            self.var_name = self.input(0).payload
+            if self.set_var_val(self.input(0).payload, self.input(1).payload):
+                self.set_output_val(0, Data(self.get_var_val(self.var_name)))
+
+    def action_make_passive(self):
+        self.active = False
+        self.delete_input(0)
+        self.delete_output(0)
+        del self.gui.actions['make passive']
+        self.gui.actions['make active'] = {'method': self.action_make_active}
+
+    def action_make_active(self):
+        self.active = True
+        self.create_input(type_='exec', pos=0)
+        self.create_output(type_='exec', pos=0)
+        del self.gui.actions['make active']
+        self.gui.actions['make passive'] = {'method': self.action_make_passive}
+
+    def get_state(self):
+        return {'active': self.active}
+
+    def set_state(self, data, version):
+        self.active = data['active']
+
+        # because otherwise he widgets won't work
+        if not self.active:
+            self.action_make_passive()
+
+
+class SetVarsPassive_Node(NodeBase):
+    """Sets the values of multiple script variables"""
+
+    version = 'v0.1'
+
+    title = 'set vars passive'
+    init_inputs = []
+    init_outputs = []
+    GUI = guis.SetVarsGui
+
+    def __init__(self, params):
+        super().__init__(params)
+
+        self.num_vars = 0
+
+    def add_var_input(self):
+        # self.create_input_dt(label='var', dtype=dtypes.String(size='l'))
+        # self.create_input_dt(label='val', dtype=dtypes.Data(size='l'))
+        self.create_input(label='var')
+        self.create_input(label='val')
+
+        self.num_vars += 1
+
+        if self.have_gui():
+            self.gui.rebuild_remove_actions()
+
+    def remove_var_input(self, number):
+        self.delete_input((number-1)*2)
+        self.delete_input((number-1)*2)
+        self.num_vars -= 1
+        # self.rebuild_remove_actions()
+
+        if self.have_gui():
+            self.gui.rebuild_remove_actions()
+
+    # def rebuild_remove_actions(self):
+    #     if not self.have_gui():
+    #         return
+    #
+    #     remove_keys = []
+    #     for k, v in self.gui.actions.items():
+    #         if k.startswith('remove var'):
+    #             remove_keys.append(k)
+    #
+    #     for k in remove_keys:
+    #         del self.gui.actions[k]
+    #
+    #     for i in range(self.num_vars):
+    #         self.gui.actions[f'remove var {i+1}'] = {
+    #             'method': self.remove_var_input,
+    #             'data': i+1
+    #         }
+
+    def update_event(self, input_called=-1):
+
+        var_names = [self.input(i).payload for i in range(0, len(self.inputs), 2)]
+        values = [self.input(i).payload for i in range(1, len(self.inputs), 2)]
+
+        for i in range(len(var_names)):
+            self.set_var_val(var_names[i], values[i])
+
+    def get_state(self):
+        return {'num vars': self.num_vars}
+
+    def set_state(self, data, version):
+        self.num_vars = data['num vars']
+
+
+export_nodes([
+    SetVar_Node,
+    GetVar_Node,
+    Val_Node,
+    Result_Node,
+    SetVarsPassive_Node
+])
diff --git a/ryven/main/packages/gui_env.py b/ryven/main/packages/gui_env.py
new file mode 100644
index 00000000..264d7c4b
--- /dev/null
+++ b/ryven/main/packages/gui_env.py
@@ -0,0 +1,44 @@
+"""
+This module automatically imports all requirements for Gui definitions of a nodes package.
+"""
+
+import inspect
+from typing import Type
+
+import ryven.gui.std_input_widgets as inp_widgets
+
+from ryvencore_qt import \
+    NodeInputWidget, NodeMainWidget, NodeGUI
+
+from ryvencore import Data, Node, serialize, deserialize
+
+def init_node_guis_env():
+    pass
+
+
+class GuiClassesRegistry:
+    """
+    Used for statically keeping the gui classes specified in export_guis to access them through node_env.import_guis().
+    """
+    exported_guis = []
+    exported_guis_sources: [[str]] = []
+
+
+class GuiClassesContainer:
+    pass
+
+
+def export_guis(guis: [Type[NodeGUI]]):
+    """
+    Exports/exposes the specified node gui classes to the nodes file importing them via import_guis().
+    Returns an object with all exported gui classes as attributes for direct access.
+    """
+
+    gcc = GuiClassesContainer()
+    for w in guis:
+        setattr(gcc, w.__name__, w)
+    GuiClassesRegistry.exported_guis.append(gcc)
+
+    # get sources
+    gui_sources = [inspect.getsource(g) for g in guis]
+    GuiClassesRegistry.exported_guis_sources.append(gui_sources)
diff --git a/ryven/main/packages/node_env.py b/ryven/main/packages/node_env.py
new file mode 100644
index 00000000..92334424
--- /dev/null
+++ b/ryven/main/packages/node_env.py
@@ -0,0 +1,116 @@
+"""
+This module automatically imports all requirements for custom nodes.
+"""
+
+import os
+from typing import Type
+
+from ryven.main.packages.nodes_package import load_from_file, NodesPackage
+
+from ryvencore import Node, NodeInputType, NodeOutputType, Data, serialize, deserialize
+
+
+def init_node_env():
+
+    # Note 1:
+    #   Because the wrapper classes were removed from ryvencore-qt recently, we don't need to import from
+    #   difference ryvencore sources here anymore depending on the mode, ryvencore stuff comes from ryvencore
+    #   directly now.
+
+    # Note 2:
+    #   I removed the NodeWrp class, which just added the actions dict to the Node base class, because it
+    #   should be moved to ryvencore soon.
+
+    # Note 3:
+    #   I removed dtypes imports, they are currently not supported and are expected to become a new add-on
+    #   some time in the future.
+
+    # Note 4:
+    #   I removed the import of NodeBase, because it's messy to override the Node class in the first place.
+
+    if os.environ['RYVEN_MODE'] == 'gui':
+        import ryvencore_qt
+
+
+def import_guis(origin_file: str, gui_file_name='gui.py'):
+    """
+    Import all exported GUI classes from gui_file_name with respect to the origin_file location.
+    Returns an object with all exported gui classes as attributes for direct access.
+    """
+
+    caller_location = os.path.dirname(origin_file)
+
+    # alternative solution without __file__ argument; does not work with debugging, so it's not the best idea
+    #   caller_location = os.path.dirname(stack()[1].filename)  # getting caller file path from stack frame
+
+    abs_path = os.path.join(caller_location, gui_file_name)
+
+    if os.environ['RYVEN_MODE'] == 'gui':
+
+        # import the gui module
+        load_from_file(abs_path)
+
+        # in GUI mode, import the gui classes container from gui_env containing all the exported gui classes
+        from ryven import gui_env
+        gui_classes_container = gui_env.GuiClassesRegistry.exported_guis[-1]
+
+    else:
+        # in non-gui mode, return an object that just returns None for all accessed attributes
+        # so guis.MyGUI in the nodes file just returns None then
+        class PlaceholderGuisContainer:
+            def __getattr__(self, item):
+                return None
+        gui_classes_container = PlaceholderGuisContainer()
+
+    return gui_classes_container
+
+
+class NodesEnvRegistry:
+    """
+    Statically stores custom `ryvencore.Node` and `ryvencore.Data` subclasses
+    exported via export_nodes on import of a nodes package.
+    After running the imported nodes.py module (which needs to call
+    `export_nodes()` to run), Ryven can then retrieve the exported types from
+    this class.
+    """
+
+    # stores, for each nodes package separately, a list of exported node types
+    exported_nodes: [[Type[Node]]] = []
+
+    # stores, for each nodes package separately, a list of exported data types
+    exported_data_types: [[Type[Data]]] = []
+
+    # stores the package that is currently being imported; set by the nodes package
+    # loader ryven.main.packages.nodes_package.import_nodes_package;
+    # needed for extending the identifiers of node types to include the package name
+    current_package: NodesPackage = None
+
+
+def export_nodes(node_types: [Type[Node]], data_types: [Type[Data]] = None):
+    """
+    Exports/exposes the specified nodes to Ryven for use in flows.
+    """
+
+    if data_types is None:
+        data_types = []
+
+    # extend identifiers of node types to include the package name
+    for node_type in node_types:
+        # store the package name as identifier prefix, which will be added
+        # by ryvencore when registering the node type
+        node_type.identifier_prefix = NodesEnvRegistry.current_package.name
+
+        # also add the identifier without the prefix as fallback for older versions
+        node_type.legacy_identifiers = [
+            *node_type.legacy_identifiers,
+            node_type.identifier if node_type.identifier else node_type.__name__
+        ]
+
+    NodesEnvRegistry.exported_nodes.append(node_types)
+    NodesEnvRegistry.exported_data_types.append(data_types)
+
+    if os.environ['RYVEN_MODE'] == 'gui':
+        # store node sources for code inspection
+        from ryven.gui.code_editor.codes_storage import register_node_type
+        for node_type in node_types:
+            register_node_type(node_type)
diff --git a/ryven/main/packages/nodes_package.py b/ryven/main/packages/nodes_package.py
new file mode 100644
index 00000000..e181adf3
--- /dev/null
+++ b/ryven/main/packages/nodes_package.py
@@ -0,0 +1,197 @@
+"""
+This module, together with the node_env and gui_env defines Ryven's nodes
+package system. It can be used outside of Ryven as well.
+"""
+
+import importlib.util
+import os
+import pathlib
+from os.path import basename, dirname, splitext, normpath, join
+from typing import Tuple, List, Type, Union, Set, Optional
+
+from ryvencore import Node, Data
+
+from ryven.main.utils import read_project, ryven_dir_path, abs_path_from_package_dir
+
+
+class NodesPackage:
+    """
+    A small container to store meta data about imported node packages.
+    """
+
+    def __init__(self, directory: str):
+
+        self.name = basename(normpath(directory))
+        self.directory = directory
+
+        self.file_path = normpath(join(directory, 'nodes.py'))
+
+    def __str__(self):
+        return f'{self.__class__.__name__}({self.name})'
+
+    def __repr__(self):
+        return f'{self.__class__.__name__}({self.name}, {self.directory})'
+
+    def __eq__(self, other):
+        if isinstance(other, NodesPackage):
+            return self.name == other.name
+        else:
+            return self.name == str(other)
+
+    def __hash__(self):
+        return hash(self.name)
+
+    def config_data(self):
+        return {
+            'name': self.name,
+            'dir': self.directory,
+        }
+
+
+def load_from_file(file: str = None, components_list: [str] = None) -> tuple:
+    """
+    Imports specified components from a python module with given file path.
+    """
+    if components_list is None:
+        components_list = []
+
+    name = basename(file).split('.')[0]
+    spec = importlib.util.spec_from_file_location(name, file)
+
+    importlib.util.module_from_spec(spec)
+
+    mod = spec.loader.load_module(name)
+    # using load_module(name) instead of exec_module(mod) here,
+    # because exec_module() somehow then registers it as "built-in"
+    # which is wrong and prevents inspect from parsing the source
+
+    comps = tuple([getattr(mod, c) for c in components_list])
+
+    return comps
+
+
+def import_nodes_package(package: NodesPackage = None, directory: str = None) -> \
+        Tuple[List[Type[Node]], List[Type[Data]]]:
+    """Loads node and data classes from a Ryven nodes package and returns both in separate lists.
+
+    Can be used without a running Ryven instance, but you need to specify in which mode nodes should be loaded
+    by setting the environment variable RYVEN_MODE to either 'gui' (gui imports enabled) or 'no-gui'.
+
+    :param package: The NodesPackage object.
+    :param directory: The path to the directory where the nodes.py file is located, used if package is None.
+    :return: A tuple containing node types (classes) first, and the data types exported by the package second.
+    """
+
+    if package is None:
+        package = NodesPackage(directory)
+
+    if 'RYVEN_MODE' not in os.environ:
+        raise Exception(
+            "Please specify the environment variable RYVEN_MODE ('gui' or 'no-gui') before loading any packages. "
+            "For example set os.environ['RYVEN_MODE'] = 'no-gui' for gui-less deployment."
+        )
+
+    from ryven import node_env
+    node_env.NodesEnvRegistry.current_package = package
+    load_from_file(package.file_path)
+
+    node_types = node_env.NodesEnvRegistry.exported_nodes[-1]
+    data_types = node_env.NodesEnvRegistry.exported_data_types[-1]
+
+    return node_types, data_types
+
+
+def process_nodes_packages(
+    project_or_nodes: Union[
+        Union[str, pathlib.Path],                       # path to Ryven project
+        List[Union[str, pathlib.Path, NodesPackage]]    # list of node packages
+    ],
+    requested_packages: List[NodesPackage] = None
+) -> Tuple[Set[NodesPackage], List[pathlib.Path], Optional[dict]]:
+    """Takes a project or list of node packages and additionally requested node
+    packages and checks whether the node packages are valid.
+
+    It also removes duplicates based on the name (and not the contents!).
+
+    :param project_or_nodes:
+        Either a path to a Ryven project or a list of node packages.
+        If a Ryven project is given, the required nodes packages specified
+        in the project file are looked for.
+        If a list is given, `NodesPackage` instances are  copied into the
+        resulting list; paths are considered to direct to 'nodes.py'.
+        If 'nodes.py' is found in the path,
+        a `NodesPackage` instance is created and added to the resulting list.
+        If 'nodes.py' cannot be found in the path, the package is searched in
+        Ryven's example nodes dir, e.g. if "std" is given and not found
+        locally, the "std" package included in Ryven is loaded.
+    :param requested_packages:
+        A list of additional node package, which were requested. These take
+        precedence over `nodes`.
+        The default is `[]`.
+
+    :return:
+        A tuple of three elements:
+            - Set of available nodes required by the project or from list of nodes.
+            - Set of nodes required by the project or from list of nodes, which could not be found.
+            - Dictionary with the contents of the project or `None`.
+    """
+    # from ryven.main.packages.nodes_package import NodesPackage
+
+    if requested_packages is None:
+        requested_packages = []
+
+    # Find nodes in the project file
+    try:
+        project_dict = read_project(project_or_nodes)
+        node_pkg_paths = [p['dir'] for p in project_dict['required packages']]
+    except TypeError:
+        project_dict = None
+        node_pkg_paths = project_or_nodes
+    except KeyError:
+        # No required packages found
+        project_dict = None
+        node_pkg_paths = []
+
+    pkgs = set()
+    pkgs_not_found = set()
+    for pkg in node_pkg_paths:
+        if isinstance(pkg, NodesPackage):
+            pkgs.add(pkg)
+        else:
+            # For backward compatibility we have to deal with Windows and Posix
+            # paths in the project's file
+            pkg_windows_path = pathlib.PureWindowsPath(pkg)
+            pkg_posix_path = pathlib.PurePosixPath(pkg)
+            if len(pkg_windows_path.parts) > len(pkg_posix_path.parts):
+                pkg_path = pathlib.Path(pkg_windows_path)
+            else:
+                pkg_path = pathlib.Path(pkg_posix_path)
+            if pkg_path.joinpath('nodes.py').exists():
+                pkgs.add(NodesPackage(str(pkg_path)))
+                continue
+
+            # Try to find the nodes package in Ryven's custom nodes dir
+            pkg_custom_path = pathlib.Path(ryven_dir_path(), 'nodes', pkg)
+            if pkg_custom_path.joinpath('nodes.py').exists():
+                pkgs.add(NodesPackage(str(pkg_custom_path)))
+                continue
+
+            # Try to find in Ryven's example nodes
+            pkg_example_path = pathlib.Path(abs_path_from_package_dir('example_nodes'), pkg)
+            if pkg_example_path.joinpath('nodes.py').exists():
+                pkgs.add(NodesPackage(str(pkg_example_path)))
+                continue
+
+            # Package could not be found
+            pkgs_not_found.add(pkg_path)
+
+    # Check, if nodes which could not be found are already available in
+    # `requested_nodes`.
+    # This check is done by comparing the path name to the nodes' names
+    args_pkgs_names = [pkg.name for pkg in requested_packages]
+    pkgs_not_found = [
+        pkg_path
+        for pkg_path in pkgs_not_found
+        if pkg_path.name not in args_pkgs_names]
+
+    return pkgs, pkgs_not_found, project_dict
diff --git a/ryven/main/utils.py b/ryven/main/utils.py
index 43793407..17ac9abc 100644
--- a/ryven/main/utils.py
+++ b/ryven/main/utils.py
@@ -1,116 +1,223 @@
-import inspect
-import os
-from os.path import normpath, join, dirname, abspath, basename, expanduser
-import importlib.util
+"""
+Core utilities for handling Ryven projects and nodes packages, and
+resolving paths. Deos not depend on any Qt modules.
+"""
 
-from ryven.main.nodes_package import NodesPackage
+from os.path import normpath, join, dirname, abspath, expanduser
+import pathlib
+from typing import Union, Optional
+from packaging.version import Version
 
 
-def load_from_file(file: str = None, components_list: [str] = []) -> tuple:
-    """
-    Imports the specified components from a python module with given file path.
-    """
-
-    name = basename(file).split('.')[0]
-    spec = importlib.util.spec_from_file_location(name, file)
-
-    importlib.util.module_from_spec(spec)
-
-    mod = spec.loader.load_module(name)
-    # using load_module(name) instead of exec_module(mod) here,
-    # because exec_module() somehow then registers it as "built-in"
-    # which is wrong and prevents inspect from parsing the source
-
-    comps = tuple([getattr(mod, c) for c in components_list])
-
-    return comps
-
+def read_project(project_path: Union[str, pathlib.Path]) -> dict:
+    """Read the project file and return its dictionary.
 
-def import_nodes_package(package: NodesPackage = None, directory: str = None) -> list:
+    :param project_path: The path to the project file.
+    :return: The contents of the project file.
     """
-    This function is an interface to the node packages system in Ryven.
-    It loads nodes from a Ryven nodes package and returns them in a list.
-    It can be used without a running Ryven instance, but you need to specify in which mode nodes should be loaded
-    by setting the environment variable RYVEN_MODE to either 'gui' (gui imports enabled) or 'no-gui'.
-    You can either pass a NodesPackage object or a path to the directory where the nodes.py file is located.
-    """
-
-    if package is None:
-        package = NodesPackage(directory)
-
-    if 'RYVEN_MODE' not in os.environ:
-        raise Exception(
-            "Please specify the environment variable RYVEN_MODE ('gui' or 'no-gui') before loading any packages. "
-            "For example set os.environ['RYVEN_MODE'] = 'no-gui' for gui-less deployment."
+    import io
+    import json
+
+    if isinstance(project_path, io.TextIOWrapper):
+        project_dict = json.loads(project_path.read(), strict=False)
+    else:
+        with open(project_path) as f:
+            import json
+            # strict=False is needed to allow 'control characters' like '\n'
+            # for newline when loading the json
+            project_dict = json.load(f, strict=False)
+
+    # backward compatibility: translate old project files to current version
+    if 'ryven version' not in project_dict['general info'] or \
+            Version(project_dict['general info']['ryven version']) <= Version('3.2'):
+        print(
+            'WARNING: project was created with an older version of Ryven.',
+            'Attempting to translate project to current version.'
         )
+        project_dict = translate_project_v3_2_0(project_dict)
+
+    return project_dict
+
+
+def translate_project_v3_2_0(p: dict):
+    def max_gid(d: dict) -> int:
+        """Recursively find the maximum GID used in the project.."""
+        n = 0
+        for k, v in d.items():
+            if isinstance(v, dict):
+                n = max(n, max_gid(v))
+            elif isinstance(v, list):
+                for e in v:
+                    if isinstance(e, dict):
+                        n = max(n, max_gid(e))
+            elif k == 'GID':
+                n = max(n, v)
+        return n
+
+    gid_ctr = max_gid(p) + 1
+    def get_gid():
+        nonlocal gid_ctr
+        gid_ctr += 1
+        return gid_ctr
+
+    def replace_item(obj, key, replace_value):
+        # https://stackoverflow.com/questions/45335445/how-to-recursively-replace-dictionary-values-with-a-matching-key
+        for k, v in obj.items():
+            if isinstance(v, dict):
+                obj[k] = replace_item(v, key, replace_value)
+        if key in obj:
+            obj[key] = replace_value
+        return obj
+
+    proj = {
+        'general info': p['general info'],
+        'required packages': p['required packages'],
+        'GID': get_gid(),
+        'version': '0.4.0',
+        'flows': {},
+        'addons': {},
+    }
+
+    variables = {}
+
+    for s in p['scripts']:
+        t = s['title']
+        vars = s['variables']
+        gid = s['flow']['GID']
+        variables[(t, gid)] = vars
+
+        proj['flows'][t] = {
+            'GID': gid,
+            'algorithm mode': s['flow']['algorithm mode'],
+            'nodes': [
+                {
+                    **n_d,
+                    # remove input widget data to prevent loading errors
+                    # because many of the nodes have new input widget
+                    # classes now
+                    'inputs': [
+                        replace_item(i, 'widget data', None)
+                        for i in n_d['inputs']
+                    ]
+                } for n_d in s['flow']['nodes']
+            ],
+            'connections': s['flow']['connections'],
+            'flow view': s['flow']['flow view'],
+            'output data': [{
+                # simply set every output to None
+                'data': {
+                    'GID': get_gid(),
+                    'identifier': 'Data',
+                    'serialized': 'gAROLg==',  # encoded 'None'
+                },
+                'dependent node outputs': [],
+            }]
+        }
+
+    proj['addons']['Variables'] = {
+        'GID': get_gid(),
+        'version': '0.4',
+        'custom state': {
+            flow_id: {
+                v: {
+                    'GID': get_gid(),
+                    'identifier': 'Data',
+                    'serialized': content['serialized'],
+                }
+                for v, content in vars.items()
+            }
+            for (flow_name, flow_id), vars in variables.items()
+        }
+    }
 
-    from ryven import NENV
-    load_from_file(package.file_path)
-
-    nodes = NENV.NodesRegistry.exported_nodes[-1]
-
-    if os.environ['RYVEN_MODE'] == 'gui':
-
-        # ADD SOURCES
-
-        # because all the node package modules are named 'nodes.py' now, we need to retrieve the sources via inspect here
-        # since inspect will be unable to do so once we imported another 'nodes' module.
+    # ignoring loggers and actions
 
-        node_cls_sources = NENV.NodesRegistry.exported_node_sources[-1]
-        node_mod_sources = [inspect.getsource(inspect.getmodule(n)) for n in nodes]
+    return proj
 
-        for i in range(len(nodes)):
-            n = nodes[i]
 
-            mw_cls_src = inspect.getsource(n.main_widget_class) if n.main_widget_class else None
-            mw_mod_src = inspect.getsource(inspect.getmodule(n.main_widget_class)) if n.main_widget_class else None
+def find_project(project_path: Union[str, pathlib.Path]) -> Optional[pathlib.Path]:
+    """Resolves a possibly *~/.ryven/saves/*-relative path to a nodes package to an absolute path.
 
-            n.__class_codes__ = {
-                'node cls': node_cls_sources[i],
-                'node mod': node_mod_sources[i],
-                'main widget cls': mw_cls_src,
-                'main widget mod': mw_mod_src,
-                'custom input widgets': {
-                    name: {
-                        'cls': inspect.getsource(inp_cls),
-                        'mod': inspect.getsource(inspect.getmodule(inp_cls))
-                    } for name, inp_cls in n.input_widget_classes.items()
-                }
-            }
+    :param project_path: The path to the project file or the subpath to :code:`ryven_dir_path()/saves`.
+        The file extension '.json' can be omitted.
+    :return: The absolute and resolved path to the project file, or `None` if it could not be found.
 
-    # -----------
-
-    # add package name to identifiers and define custom types
+    """
+    project_path = pathlib.Path(project_path)
+
+    if project_path.exists():
+        return project_path.resolve()
+    elif project_path.with_suffix('.json').exists():
+        return project_path.with_suffix('.json').resolve()
+    else:
+        project_path = pathlib.Path(ryven_dir_path(), 'saves', project_path)
+        if project_path.exists():
+            return project_path.resolve()
+        elif project_path.with_suffix('.json').exists():
+            return project_path.with_suffix('.json').resolve()
+        else:
+            return None
+
+
+def find_config_file(cfg_file_path: str) -> Optional[pathlib.Path]:
+    """Resolves a possibly *~/.ryven/*-relative path of a config file to an absolute path.
+
+    :param cfg_file_path: Either an absolute path, or relative to the *~/.ryven/* directory.
+        The file extension '.cfg' can be omitted.
+    :return: The full path to the config file or `None`, if it could not be found.
+    """
 
-    for n in nodes:
-        n.identifier_prefix = package.name  #  + '.' + (n.identifier if n.identifier else n.__name__)
-        n.type_ = package.name if not n.type_ else package.name+f'[{n.type_}]'
+    config_file_path = pathlib.Path(cfg_file_path)
 
-    return nodes
+    if config_file_path.exists():
+        return config_file_path.resolve()
+    else:
+        config_file_path = pathlib.Path(ryven_dir_path(), cfg_file_path)
+        if config_file_path.exists():
+            return config_file_path.resolve()
+        else:
+            return None
 
 
 def ryven_dir_path() -> str:
     """
     :return: absolute path the (OS-specific) '~/.ryven/' folder
     """
-    return normpath(join(expanduser('~'), '.ryven/'))
+    return abspath(normpath(join(expanduser('~'), '.ryven/')))
 
 
-def abs_path_from_package_dir(path_rel_to_ryven: str):
-    """Given a path string relative to the ryven package, return the file/folder absolute path
-
-    :param path_rel_to_ryven: path relative to ryven package (e.g. main/NENV.py)
-    :type path_rel_to_ryven: str
+def abs_path_from_package_dir(ryven_rel_path: str):
+    """
+    :param ryven_rel_path: path relative to ryven package folder (e.g. main/node_env.py)
+    :return: absolute path
     """
     ryven_path = dirname(dirname(__file__))
-    return abspath(join(ryven_path, path_rel_to_ryven))
+    return abspath(join(ryven_path, ryven_rel_path))
 
 
-def abs_path_from_ryven_dir(path_rel_to_ryven_dir: str):
-    """Given a path string relative to the ryven dir '~/.ryven/', return the file/folder absolute path
+def abs_path_from_ryven_dir(ryven_rel_path: str):
+    """
+    :param ryven_rel_path: path relative to '~/.ryven/' dir (e.g. saves)
+    :return: absolute path
+    """
 
-    :param path_rel_to_ryven_dir: path relative to ryven dir (e.g. saves)
-    :return: file/folder absolute path
+    return abspath(join(ryven_dir_path(), ryven_rel_path))
+
+
+def ryven_version() -> Version:
+    """
+    :return: the version of Ryven
     """
 
-    return abspath(join(ryven_dir_path(), path_rel_to_ryven_dir))
+    # if we are in a development environment, we can't use importlib.metadata
+    if (pathlib.Path(abs_path_from_package_dir('../setup.cfg'))).exists():
+        # read the version from setup.cfg
+        import configparser
+        config = configparser.ConfigParser()
+        config.read(abs_path_from_package_dir('../setup.cfg'))
+        ver = Version(config['metadata']['version'])
+        return ver
+    else:
+        # read the version from importlib.metadata
+        from importlib.metadata import version
+        return Version(version('ryven'))
diff --git a/ryven/node_env.py b/ryven/node_env.py
new file mode 100644
index 00000000..753a8197
--- /dev/null
+++ b/ryven/node_env.py
@@ -0,0 +1,2 @@
+# re-exporting the node_env module from ryven.main.packages.node_env
+from ryven.main.packages.node_env import *
diff --git a/ryven/resources/pics/Ryven_icon.jpg b/ryven/resources/pics/Ryven_icon.jpg
new file mode 100644
index 00000000..68a29989
Binary files /dev/null and b/ryven/resources/pics/Ryven_icon.jpg differ
diff --git a/setup.cfg b/setup.cfg
index d9bd4887..315b43d7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,12 +1,13 @@
 [metadata]
 name = ryven
-version = v3.1.1
+version = 3.4.0a1
 author = Leon Thomm
 author_email = l.thomm@mailbox.org
 description = Flow-based visual scripting for Python
 long_description = file: README.md
 long_description_content_type = text/markdown
-license_file = LICENSE
+license_files =
+    LICENSE
 url = https://github.com/leon-thomm/Ryven
 project_urls =
     Website = https://ryven.org
@@ -20,13 +21,14 @@ packages = find:
 include_package_data = True
 python_requires = >=3.6, !=3.10
 install_requires =
-    ryvencore-qt ==0.3.1.*
-    ryvencore ==0.3.1.*
+    ryvencore-qt ==0.4.*
+    ryvencore ==0.4.*
     Jinja2
     Pygments
     textdistance
+    packaging
 
 [options.entry_points]
 console_scripts =
     ryven = ryven:run_ryven
-    ryven_console = ryven:run_ryven_console
+    ryven-console = ryven:run_ryven_console
diff --git a/setup.py b/setup.py
index 2eb1a8af..e8f9d2bd 100644
--- a/setup.py
+++ b/setup.py
@@ -9,3 +9,18 @@
     os.mkdir(path)
     os.mkdir(os.path.join(path, 'nodes/'))
     os.mkdir(os.path.join(path, 'saves/'))
+
+# generate default config files if not existent yet
+cfg_path = os.path.join(path, 'ryven.cfg')
+if not os.path.exists(cfg_path):
+    with open(cfg_path, 'w') as f:
+        f.write("""
+# Automatically loaded default Ryven config file
+
+# EXAMPLE:
+# the following configuration launches Ryven in 'light' mode with flow theme 'colorful light' and node packages 'std' and 'linalg' preloaded
+#     window-theme: light
+#     flow-theme: "colorful light"
+#     nodes: std
+#     nodes: linalg
+""")


Test patch:
diff --git a/tests/built_in_nodes.py b/tests/built_in_nodes.py
new file mode 100644
index 00000000..4d39161e
--- /dev/null
+++ b/tests/built_in_nodes.py
@@ -0,0 +1,27 @@
+import os
+import unittest
+from os.path import join, dirname
+
+import ryvencore as rc
+from ryven.node_env import init_node_env
+from ryven.main.packages.nodes_package import NodesPackage
+from ryven import import_nodes_package
+
+
+class BuiltInNodesTestCase(unittest.TestCase):
+
+    def test_load_builtin_nodes(self):
+        os.environ['RYVEN_MODE'] = 'no-gui'
+        init_node_env()
+        session = rc.Session(gui=False)
+        nodes, data_types = import_nodes_package(NodesPackage(
+            directory=join(dirname(__file__), '../ryven/main/packages/built_in/')
+        ))
+        session.register_data_types(data_types)
+        session.register_node_types(nodes)
+        print('registered nodes: ', nodes)
+
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/console.py b/tests/console.py
new file mode 100644
index 00000000..a9d8bc0f
--- /dev/null
+++ b/tests/console.py
@@ -0,0 +1,38 @@
+import json
+import unittest
+
+import ryvencore as rc
+from ryven.main.RyvenConsole import load_project_and_nodes
+
+
+class RyvenConsoleTestCase(unittest.TestCase):
+    # This class tests the working of the underlying methods used by RyvenConsole, i.e. that do not use
+    # the GUI elements of Ryven.
+
+    def test_load_empty_flow(self):
+        # Create empty flow
+        rc.Base.Base._global_id_ctr.ctr = -1
+        session = rc.Session()
+        session.create_flow('main')
+        serialize = session.serialize()
+        json_string = json.dumps(serialize)
+        del session
+
+        test_data_path = ".\\data\\test.json"
+        with open(test_data_path, "w") as f:
+            # Writing data to a file
+            f.write(json_string)
+
+        # Given how the Global IDs work (they are re-generated every time an object is created)
+        # we reset the counter manually to ensure the IDs are the same
+        rc.Base.Base._global_id_ctr.ctr = -1
+
+        # Reload file
+        session = rc.Session()
+        load_project_and_nodes(test_data_path, [], None, session)
+
+        self.assertEqual(serialize, session.serialize())
+
+
+if __name__ == '__main__':
+    unittest.main()