diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml
new file mode 100644
index 0000000000000000000000000000000000000000..52b45d9f0966e59af82ec774a6669d62f0e71168
--- /dev/null
+++ b/.github/workflows/nix.yml
@@ -0,0 +1,19 @@
+name: "CI - Nix"
+
+on:
+  push:
+
+jobs:
+  nix:
+    runs-on: "${{ matrix.os }}-latest"
+    strategy:
+      matrix:
+        os: [ubuntu, macos]
+    steps:
+      - uses: actions/checkout@v4
+      - uses: cachix/install-nix-action@v27
+      - uses: cachix/cachix-action@v15
+        with:
+          name: gepetto
+          authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
+      - run: nix build -L
diff --git a/.mergify.yml b/.mergify.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0e037adff9376be82c56b03a0419a0b55e5a98df
--- /dev/null
+++ b/.mergify.yml
@@ -0,0 +1,12 @@
+pull_request_rules:
+  - name: merge automatically when CI passes and PR is approved
+    conditions:
+      - check-success = "gitlab-ci"
+      - check-success = "nix (macos)"
+      - check-success = "nix (ubuntu)"
+      - check-success = "pre-commit.ci - pr"
+      - or:
+        - author = pre-commit-ci[bot]
+        - author = dependabot[bot]
+    actions:
+      merge:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 263f1be0f4191473fce548b81eba202bcef49965..ae52b97ea34616a57193e40ef1a293e45a67fd45 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,7 +2,7 @@ ci:
   autoupdate_branch: devel
 repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.5.5
+  rev: v0.6.9
   hooks:
   - id: ruff
     args:
@@ -19,13 +19,13 @@ repos:
   - id: toml-sort-fix
     exclude: poetry.lock
 - repo: https://github.com/pre-commit/mirrors-clang-format
-  rev: v18.1.8
+  rev: v19.1.1
   hooks:
   - id: clang-format
     args:
     - --style=Google
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.6.0
+  rev: v5.0.0
   hooks:
   - id: check-added-large-files
   - id: check-ast
diff --git a/examples/notebooks/a-basics.ipynb b/examples/notebooks/a-basics.ipynb
index ee4585fe4d1812e719cf548693a6dd65d883a45c..8f91be62ae61209466b7b0e6420160cde9692e31 100644
--- a/examples/notebooks/a-basics.ipynb
+++ b/examples/notebooks/a-basics.ipynb
@@ -1,133 +1,106 @@
 {
-  "cells": [
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Launch the gui and create a window."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 6,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from gepetto.corbaserver import gui_client, start_server, Color\n",
-    "\n",
-    "start_server(args=[])\n",
-    "gui = gui_client(window_name=\"tuto\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Add some visual entities\n",
-    "\n",
-    "They are called nodes and are organised as a tree.\n",
-    "\n",
-    "When you create a window, a group node (called *group* from now on) is created with the same name. Adding nodes to this group displays them in the window."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 20,
-   "metadata": {},
-   "outputs": [
+  "cells" : [
     {
-     "data": {
-      "text/plain": [
-       "True"
+      "cell_type" : "markdown",
+      "metadata" : {},
+      "source" : ["# Launch the gui and create a window."]
+    },
+    {
+      "cell_type" : "code",
+      "execution_count" : 6,
+      "metadata" : {},
+      "outputs" : [],
+      "source" : [
+        "from gepetto.corbaserver import Color, gui_client, start_server\n",
+        "\n", "start_server(args=[])\n",
+        "gui = gui_client(window_name=\"tuto\")"
       ]
-     },
-     "execution_count": 20,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "gui.addBox(\"box\", 0.1, 0.1, 0.1, Color.green)\n",
-    "gui.addToGroup(\"box\", \"tuto\")\n",
-    "gui.setCameraToBestFit(\"tuto\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "Add another node, directly in group *tuto*."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 15,
-   "metadata": {},
-   "outputs": [
+    },
     {
-     "data": {
-      "text/plain": [
-       "True"
+      "cell_type" : "markdown",
+      "metadata" : {},
+      "source" : [
+        "# Add some visual entities\n", "\n",
+        "They are called nodes and are organised as a tree.\n", "\n",
+        "When you create a window, a group node (called *group* from now on) ",
+        "is created with the same name. Adding nodes to this group displays ",
+        "them in the window."
       ]
-     },
-     "execution_count": 15,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "gui.addCylinder(\"tuto/cyl\", 0.01, 0.1, Color.red)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "To move one node, you must use `applyConfiguration` and then `refresh`. All calls to `applyConfiguration` are applied at the next `refresh`."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 19,
-   "metadata": {},
-   "outputs": [
+    },
+    {
+      "cell_type" : "code",
+      "execution_count" : 20,
+      "metadata" : {},
+      "outputs" : [ {
+        "data" : {"text/plain" : ["True"]},
+        "execution_count" : 20,
+        "metadata" : {},
+        "output_type" : "execute_result"
+      } ],
+      "source" : [
+        "gui.addBox(\"box\", 0.1, 0.1, 0.1, Color.green)\n",
+        "gui.addToGroup(\"box\", \"tuto\")\n",
+        "gui.setCameraToBestFit(\"tuto\")"
+      ]
+    },
+    {
+      "cell_type" : "markdown",
+      "metadata" : {},
+      "source" : ["Add another node, directly in group *tuto*."]
+    },
     {
-     "data": {
-      "text/plain": [
-       "True"
+      "cell_type" : "code",
+      "execution_count" : 15,
+      "metadata" : {},
+      "outputs" : [ {
+        "data" : {"text/plain" : ["True"]},
+        "execution_count" : 15,
+        "metadata" : {},
+        "output_type" : "execute_result"
+      } ],
+      "source" : ["gui.addCylinder(\"tuto/cyl\", 0.01, 0.1, Color.red)"]
+    },
+    {
+      "cell_type" : "markdown",
+      "metadata" : {},
+      "source" : [
+        "To move one node, you must use `applyConfiguration` and ",
+        "then `refresh`. All calls to `applyConfiguration` are ",
+        "applied at the next `refresh`."
+      ]
+    },
+    {
+      "cell_type" : "code",
+      "execution_count" : 19,
+      "metadata" : {},
+      "outputs" : [ {
+        "data" : {"text/plain" : ["True"]},
+        "execution_count" : 19,
+        "metadata" : {},
+        "output_type" : "execute_result"
+      } ],
+      "source" : [
+        "gui.applyConfiguration(\"tuto/cyl\", [0, 0, 0.1, 0, 0, 0, 1])\n",
+        "gui.refresh()\n", "gui.setCameraToBestFit(\"tuto\")"
       ]
-     },
-     "execution_count": 19,
-     "metadata": {},
-     "output_type": "execute_result"
     }
-   ],
-   "source": [
-    "gui.applyConfiguration(\"tuto/cyl\", [0, 0, 0.1, 0, 0, 0, 1])\n",
-    "gui.refresh()\n",
-    "gui.setCameraToBestFit(\"tuto\")"
-   ]
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3",
-   "language": "python",
-   "name": "python3"
+  ],
+  "metadata" : {
+    "kernelspec" : {
+      "display_name" : "Python 3",
+      "language" : "python",
+      "name" : "python3"
+    },
+    "language_info" : {
+      "codemirror_mode" : {"name" : "ipython", "version" : 3},
+      "file_extension" : ".py",
+      "mimetype" : "text/x-python",
+      "name" : "python",
+      "nbconvert_exporter" : "python",
+      "pygments_lexer" : "ipython3",
+      "version" : "3.6.9"
+    }
   },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.6.9"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2
+  "nbformat" : 4,
+  "nbformat_minor" : 2
 }
diff --git a/examples/notebooks/b-display-force-or-velocities.ipynb b/examples/notebooks/b-display-force-or-velocities.ipynb
index 5186e41d9bfc76f4c1e1d0420d3b3bf7c4d4c6f5..f9d9b1e8001538703050450dcb84e8c09ee06095 100644
--- a/examples/notebooks/b-display-force-or-velocities.ipynb
+++ b/examples/notebooks/b-display-force-or-velocities.ipynb
@@ -1,102 +1,80 @@
 {
-  "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from gepetto.corbaserver import gui_client, start_server, Color\n",
-    "\n",
-    "start_server(args=[])\n",
-    "gui = gui_client(window_name=\"tuto\")\n",
-    "\n",
-    "gui.createGroup(\"frame\")\n",
-    "gui.addToGroup(\"frame\", \"tuto\")\n",
-    "gui.addLandmark(\"frame\", 0.01)\n",
-    "gui.applyConfiguration(\"frame\", [0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 1.0])\n",
-    "gui.refresh()"
-   ]
+  "cells" : [
+    {
+      "cell_type" : "code",
+      "execution_count" : 1,
+      "metadata" : {},
+      "outputs" : [],
+      "source" : [
+        "from gepetto.corbaserver import Color, gui_client, start_server\n",
+        "\n", "start_server(args=[])\n",
+        "gui = gui_client(window_name=\"tuto\")\n", "\n",
+        "gui.createGroup(\"frame\")\n", "gui.addToGroup(\"frame\", \"tuto\")\n",
+        "gui.addLandmark(\"frame\", 0.01)\n",
+        "gui.applyConfiguration(\"frame\", [0.0, 0.5, 0.0, 0.0, 0.0, 0.0, ",
+        "1.0])\n", "gui.refresh()"
+      ]
+    },
+    {
+      "cell_type" : "markdown",
+      "metadata" : {},
+      "source" : [
+        "# Display a 3D vector as a linear or rotational arrow\n", "\n",
+        "The green and blue shape will represent the same vector."
+      ]
+    },
+    {
+      "cell_type" : "code",
+      "execution_count" : 2,
+      "metadata" : {},
+      "outputs" : [],
+      "source" : [
+        "from gepetto.corbaserver import tools\n", "\n",
+        "lin = tools.Linear(\"frame/lin_arrow\", color=Color.green)\n",
+        "lin.create(gui)\n", "\n",
+        "ang = tools.Angular(\"frame/ang_arrow\", color=Color.blue)\n",
+        "ang.create(gui)\n", "\n", "vel_in_local_frame = [\n", "    0.2,\n",
+        "    0.3,\n", "    0.5,\n", "]\n", "lin.set(gui, vel_in_local_frame)\n",
+        "ang.set(gui, vel_in_local_frame)\n", "\n",
+        "# This call is required.\n", "gui.refresh()"
+      ]
+    },
+    {
+      "cell_type" : "markdown",
+      "metadata" : {},
+      "source" : ["# Display a 6D vector"]
+    },
+    {
+      "cell_type" : "code",
+      "execution_count" : 3,
+      "metadata" : {},
+      "outputs" : [],
+      "source" : [
+        "from gepetto.corbaserver import tools\n", "\n",
+        "# angFactor is the maximum norm of the angular vector.\n",
+        "vector6 = tools.Vector6(\"frame\", angFactor=3.14)\n",
+        "vector6.create(gui)\n", "\n",
+        "vel_in_local_frame = [0.1, 0.0, 0.2, 0.3, 0.5, 0.0]\n",
+        "vector6.set(gui, vel_in_local_frame)\n", "gui.refresh()"
+      ]
+    }
+  ],
+  "metadata" : {
+    "kernelspec" : {
+      "display_name" : "Python 3",
+      "language" : "python",
+      "name" : "python3"
+    },
+    "language_info" : {
+      "codemirror_mode" : {"name" : "ipython", "version" : 3},
+      "file_extension" : ".py",
+      "mimetype" : "text/x-python",
+      "name" : "python",
+      "nbconvert_exporter" : "python",
+      "pygments_lexer" : "ipython3",
+      "version" : "3.6.9"
+    }
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Display a 3D vector as a linear or rotational arrow\n",
-    "\n",
-    "The green and blue shape will represent the same vector."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 2,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from gepetto.corbaserver import tools\n",
-    "\n",
-    "lin = tools.Linear(\"frame/lin_arrow\", color=Color.green)\n",
-    "lin.create(gui)\n",
-    "\n",
-    "ang = tools.Angular(\"frame/ang_arrow\", color=Color.blue)\n",
-    "ang.create(gui)\n",
-    "\n",
-    "vel_in_local_frame = [\n",
-    "    0.2,\n",
-    "    0.3,\n",
-    "    0.5,\n",
-    "]\n",
-    "lin.set(gui, vel_in_local_frame)\n",
-    "ang.set(gui, vel_in_local_frame)\n",
-    "\n",
-    "# This call is required.\n",
-    "gui.refresh()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Display a 6D vector"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 3,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from gepetto.corbaserver import tools\n",
-    "\n",
-    "# angFactor is the maximum norm of the angular vector.\n",
-    "vector6 = tools.Vector6(\"frame\", angFactor=3.14)\n",
-    "vector6.create(gui)\n",
-    "\n",
-    "vel_in_local_frame = [0.1, 0.0, 0.2, 0.3, 0.5, 0.0]\n",
-    "vector6.set(gui, vel_in_local_frame)\n",
-    "gui.refresh()"
-   ]
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.6.9"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2
+  "nbformat" : 4,
+  "nbformat_minor" : 2
 }
diff --git a/examples/pyplugin/pythonwidget.py b/examples/pyplugin/pythonwidget.py
index c31fd5c2f9fed5a98be49afa54bc19c79e5d4c69..ec88c0528a113f5772f516b93f7152b3eabfee95 100644
--- a/examples/pyplugin/pythonwidget.py
+++ b/examples/pyplugin/pythonwidget.py
@@ -1,6 +1,7 @@
-from gepetto.corbaserver import Client
 from PythonQt import Qt, QtGui
 
+from gepetto.corbaserver import Client
+
 
 class _NodeCreator(QtGui.QWidget):
     """This class represents one special tab of the new QDockWidget."""
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000000000000000000000000000000000000..3fb6ced51fe6972e9b6d838e8fecb1082f4b7296
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,58 @@
+{
+  "nodes": {
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": "nixpkgs-lib"
+      },
+      "locked": {
+        "lastModified": 1727826117,
+        "narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1728018373,
+        "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-lib": {
+      "locked": {
+        "lastModified": 1727825735,
+        "narHash": "sha256-0xHYkMkeLVQAMa7gvkddbPqpxph+hDzdu1XdGPJR+Os=",
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs/archive/fb192fec7cc7a4c26d51779e9bab07ce6fa5597a.tar.gz"
+      },
+      "original": {
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs/archive/fb192fec7cc7a4c26d51779e9bab07ce6fa5597a.tar.gz"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-parts": "flake-parts",
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000000000000000000000000000000000000..c4191bbfcbe26e32118119564d3e901a2bb38495
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,41 @@
+{
+  description = "An user-friendly Graphical Interface";
+
+  inputs = {
+    flake-parts.url = "github:hercules-ci/flake-parts";
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+  };
+
+  outputs =
+    inputs:
+    inputs.flake-parts.lib.mkFlake { inherit inputs; } {
+      systems = inputs.nixpkgs.lib.systems.flakeExposed;
+      perSystem =
+        { pkgs, self', ... }:
+        {
+          devShells.default = pkgs.mkShell { inputsFrom = [ self'.packages.default ]; };
+          packages = {
+            default = self'.packages.gepetto-viewer-corba;
+            gepetto-viewer-corba = pkgs.python3Packages.gepetto-viewer-corba.overrideAttrs (_: {
+              # TODO: remove this after next release
+              postPatch = "";
+              src = pkgs.lib.fileset.toSource {
+                root = ./.;
+                fileset = pkgs.lib.fileset.unions [
+                  ./blender
+                  ./CMakeLists.txt
+                  ./doc
+                  ./examples
+                  ./idl
+                  ./include
+                  ./package.xml
+                  ./plugins
+                  ./src
+                  ./tests
+                ];
+              };
+            });
+          };
+        };
+    };
+}