diff --git a/src/Model/AdditionalFile/AddFile.py b/src/Model/AdditionalFile/AddFile.py
index 640992055ca63fabaf560e80839cf96addb469eb..c6888132aa08bc2dfb2ad9debea15b2ef7052f7b 100644
--- a/src/Model/AdditionalFile/AddFile.py
+++ b/src/Model/AdditionalFile/AddFile.py
@@ -47,6 +47,30 @@ class AddFile(SQLSubModel):
 
         AddFile._id_cnt = max(id, AddFile._id_cnt+1)
 
+    def __getitem__(self, key):
+        value = None
+
+        if key == "enabled":
+            value = self._enabled
+        elif key == "name":
+            value = self._name
+        elif key == "path":
+            value = self._path
+        elif key == "text":
+            value = self._text
+
+        return value
+
+    def __setitem__(self, key, value):
+        if key == "enabled":
+            self._enabled = value
+        elif key == "name":
+            self._name = value
+        elif key == "path":
+            self._path = value
+        elif key == "text":
+            self._text = value
+
     @property
     def enabled(self):
         return self._enabled
diff --git a/src/Model/REPLine/REPLine.py b/src/Model/REPLine/REPLine.py
index aea23d8cd3cdb0e5b4d0ebd378b2a1b8cdfca12f..17c084b1350488d552b98a28c7a0d7691b7053b7 100644
--- a/src/Model/REPLine/REPLine.py
+++ b/src/Model/REPLine/REPLine.py
@@ -47,6 +47,30 @@ class REPLine(SQLSubModel):
 
         REPLine._id_cnt = max(id, REPLine._id_cnt+1)
 
+    def __getitem__(self, key):
+        value = None
+
+        if key == "enabled":
+            value = self._enabled
+        elif key == "name":
+            value = self._name
+        elif key == "line":
+            value = self._line
+        elif key == "solvers":
+            value = self._solvers
+
+        return value
+
+    def __setitem__(self, key, value):
+        if key == "enabled":
+            self._enabled = value
+        elif key == "name":
+            self._name = value
+        elif key == "line":
+            self._line = value
+        elif key == "solvers":
+            self._solvers = value
+
     @property
     def enabled(self):
         return self._enabled
diff --git a/src/View/AdditionalFiles/Edit/Window.py b/src/View/AdditionalFiles/Edit/Window.py
index 6db27942a885a21858def1b5e3b0fcec1791b38e..b6c82862ec0c3ff5dc22c4dc17df1f8a1715c3af 100644
--- a/src/View/AdditionalFiles/Edit/Window.py
+++ b/src/View/AdditionalFiles/Edit/Window.py
@@ -27,6 +27,9 @@ from PyQt5.QtWidgets import (
 )
 
 from View.AdditionalFiles.Translate import AddFileTranslate
+from View.AdditionalFiles.UndoCommand import (
+    SetCommand
+)
 
 logger = logging.getLogger()
 
@@ -36,7 +39,7 @@ class EditAddFileWindow(PamhyrWindow):
     _pamhyr_name = "Edit additional file"
 
     def __init__(self, study=None, config=None, add_file=None,
-                 trad=None, parent=None):
+                 trad=None, undo=None, parent=None):
 
         name = trad[self._pamhyr_name] + " - " + study.name
         super(EditAddFileWindow, self).__init__(
@@ -50,6 +53,8 @@ class EditAddFileWindow(PamhyrWindow):
         self._add_file = add_file
         self._hash_data.append(self._add_file)
 
+        self._undo = undo
+
         self.setup_values()
         self.setup_connection()
 
@@ -71,10 +76,15 @@ class EditAddFileWindow(PamhyrWindow):
         path = self.get_line_edit_text("lineEdit_path")
         text = self.get_plaintext_edit_text("plainTextEdit")
 
-        self._add_file.enabled = is_enabled
-        self._add_file.name = name
-        self._add_file.path = path
-        self._add_file.text = text
+        self._undo.push(
+            SetCommand(
+                self._add_file,
+                enabled=is_enabled,
+                name=name,
+                path=path,
+                text=text,
+            )
+        )
 
         self._propagate_update(key=Modules.ADDITIONAL_FILES)
         self.close()
diff --git a/src/View/AdditionalFiles/List.py b/src/View/AdditionalFiles/List.py
index 374ce54c672fa3ecff0094f1f777e12f0b4b54f2..517557339498cc1c784d9f2941c2b7f8416fba20 100644
--- a/src/View/AdditionalFiles/List.py
+++ b/src/View/AdditionalFiles/List.py
@@ -30,6 +30,9 @@ from PyQt5.QtGui import (
 )
 
 from View.Tools.PamhyrList import PamhyrListModel
+from View.AdditionalFiles.UndoCommand import (
+    AddCommand, DelCommand
+)
 
 logger = logging.getLogger()
 
@@ -62,10 +65,17 @@ class ListModel(PamhyrListModel):
         return QVariant()
 
     def add(self, row):
-        self._data.new(row)
+        self._undo.push(
+            AddCommand(
+                self._data, row
+            )
+        )
         self.update()
 
-    def delete(self, rows):
-        logger.info(f"add_files: delete {rows}")
-        self._data.delete_i(rows)
+    def delete(self, row):
+        self._undo.push(
+            DelCommand(
+                self._data, row
+            )
+        )
         self.update()
diff --git a/src/View/AdditionalFiles/UndoCommand.py b/src/View/AdditionalFiles/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba417ff6e03b96d6fe26f15afebf3eb5e27bde9b
--- /dev/null
+++ b/src/View/AdditionalFiles/UndoCommand.py
@@ -0,0 +1,80 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+
+class SetCommand(QUndoCommand):
+    def __init__(self, add_file, **kwargs):
+        QUndoCommand.__init__(self)
+
+        self._add_file = add_file
+        self._new = kwargs
+        self._old = None
+
+    def undo(self):
+        f = self._add_file
+
+        for key in self._old:
+            f[key] = self._old[key]
+
+    def redo(self):
+        f = self._add_file
+
+        if self._old is None:
+            self._old = {}
+            for key in self._new:
+                self._old[key] = f[key]
+
+        for key in self._new:
+            f[key] = self._new[key]
+
+class AddCommand(QUndoCommand):
+    def __init__(self, files, row):
+        QUndoCommand.__init__(self)
+
+        self._files = files
+        self._row = row
+        self._new = None
+
+    def undo(self):
+        self._files.delete([self._new])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._files.new(self._row)
+        else:
+            self._files.insert(self._row, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, files, row):
+        QUndoCommand.__init__(self)
+
+        self._files = files
+        self._row = row
+        self._old = self._files.get(row)
+
+    def undo(self):
+        self._files.insert(self._row, self._old)
+
+    def redo(self):
+        self._files.delete_i([self._row])
diff --git a/src/View/AdditionalFiles/Window.py b/src/View/AdditionalFiles/Window.py
index 6cd84203030dd8ed8c1fb861edeb904bc77c07bf..c6c22522f421c1cda65e97551c1452b80ce7af0e 100644
--- a/src/View/AdditionalFiles/Window.py
+++ b/src/View/AdditionalFiles/Window.py
@@ -28,7 +28,6 @@ from View.AdditionalFiles.List import ListModel
 from View.AdditionalFiles.Translate import AddFileTranslate
 from View.AdditionalFiles.Edit.Window import EditAddFileWindow
 
-
 class AddFileListWindow(PamhyrWindow):
     _pamhyr_ui = "AdditionalFileList"
     _pamhyr_name = "Additional files"
@@ -55,6 +54,8 @@ class AddFileListWindow(PamhyrWindow):
         self._list = ListModel(
             list_view=lst,
             data=self._study.river.additional_files,
+            undo=self._undo_stack,
+            trad=self._trad,
         )
 
     def setup_connections(self):
@@ -80,7 +81,10 @@ class AddFileListWindow(PamhyrWindow):
 
     def delete(self):
         rows = self.selected_rows()
-        self._list.delete(rows)
+        if len(rows) == 0:
+            return
+
+        self._list.delete(rows[0])
 
     def edit(self):
         rows = self.selected_rows()
@@ -99,6 +103,15 @@ class AddFileListWindow(PamhyrWindow):
                 config=self._config,
                 add_file=add_file,
                 trad=self._trad,
+                undo=self._undo_stack,
                 parent=self,
             )
             win.show()
+
+    def _undo(self):
+        self._undo_stack.undo()
+        self.update()
+
+    def _redo(self):
+        self._undo_stack.redo()
+        self.update()
diff --git a/src/View/REPLines/Edit/Window.py b/src/View/REPLines/Edit/Window.py
index 120a0a295dbbce69334e7e64f54173cf8dcd300e..473ab63dec109207351abfd01e58211b0c296059 100644
--- a/src/View/REPLines/Edit/Window.py
+++ b/src/View/REPLines/Edit/Window.py
@@ -27,6 +27,9 @@ from PyQt5.QtWidgets import (
 )
 
 from View.REPLines.Translate import REPLineTranslate
+from View.REPLines.UndoCommand import (
+    SetCommand
+)
 
 logger = logging.getLogger()
 
@@ -36,7 +39,7 @@ class EditREPLineWindow(PamhyrDialog):
     _pamhyr_name = "Edit Mage REP lines"
 
     def __init__(self, study=None, config=None, rep_line=None,
-                 trad=None, parent=None):
+                 trad=None, undo=None, parent=None):
 
         name = trad[self._pamhyr_name] + " - " + study.name
         super(EditREPLineWindow, self).__init__(
@@ -50,6 +53,8 @@ class EditREPLineWindow(PamhyrDialog):
         self._rep_line = rep_line
         self._hash_data.append(self._rep_line)
 
+        self._undo = undo
+
         self.setup_values()
 
     def setup_values(self):
@@ -61,11 +66,17 @@ class EditREPLineWindow(PamhyrDialog):
         is_enabled = self.get_check_box("checkBox_enabled")
         name = self.get_line_edit_text("lineEdit_name")
         line = self.get_line_edit_text("lineEdit_line")
-
-        self._rep_line.enabled = is_enabled
-        self._rep_line.name = name
-        self._rep_line.line = line
-        self._rep_line.solvers = set()
+        solvers = set()
+
+        self._undo.push(
+            SetCommand(
+                self._rep_line,
+                enabled=is_enabled,
+                name=name,
+                line=line,
+                solvers=solvers,
+            )
+        )
 
         self._propagate_update(key=Modules.ADDITIONAL_FILES)
         self.close()
diff --git a/src/View/REPLines/List.py b/src/View/REPLines/List.py
index a591a4b1436c87d7236d7a7df983afa382175e60..b1e1752c9dbd58a9f4a4765e4a811bd10ba2d462 100644
--- a/src/View/REPLines/List.py
+++ b/src/View/REPLines/List.py
@@ -30,6 +30,9 @@ from PyQt5.QtGui import (
 )
 
 from View.Tools.PamhyrList import PamhyrListModel
+from View.REPLines.UndoCommand import (
+    AddCommand, DelCommand
+)
 
 logger = logging.getLogger()
 
@@ -62,10 +65,17 @@ class ListModel(PamhyrListModel):
         return QVariant()
 
     def add(self, row):
-        self._data.new(row)
+        self._undo.push(
+            AddCommand(
+                self._data, row
+            )
+        )
         self.update()
 
-    def delete(self, rows):
-        logger.info(f"add_files: delete {rows}")
-        self._data.delete_i(rows)
+    def delete(self, row):
+        self._undo.push(
+            DelCommand(
+                self._data, row
+            )
+        )
         self.update()
diff --git a/src/View/REPLines/UndoCommand.py b/src/View/REPLines/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..7618c207f4c3c74164575e09b491e30e49bdc851
--- /dev/null
+++ b/src/View/REPLines/UndoCommand.py
@@ -0,0 +1,80 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+
+class SetCommand(QUndoCommand):
+    def __init__(self, rep_line, **kwargs):
+        QUndoCommand.__init__(self)
+
+        self._rep_line = rep_line
+        self._new = kwargs
+        self._old = None
+
+    def undo(self):
+        f = self._rep_line
+
+        for key in self._old:
+            f[key] = self._old[key]
+
+    def redo(self):
+        f = self._rep_line
+
+        if self._old is None:
+            self._old = {}
+            for key in self._new:
+                self._old[key] = f[key]
+
+        for key in self._new:
+            f[key] = self._new[key]
+
+class AddCommand(QUndoCommand):
+    def __init__(self, lines, row):
+        QUndoCommand.__init__(self)
+
+        self._lines = lines
+        self._row = row
+        self._new = None
+
+    def undo(self):
+        self._lines.delete([self._new])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._lines.new(self._row)
+        else:
+            self._lines.insert(self._row, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, lines, row):
+        QUndoCommand.__init__(self)
+
+        self._lines = lines
+        self._row = row
+        self._old = self._lines.get(row)
+
+    def undo(self):
+        self._lines.insert(self._row, self._old)
+
+    def redo(self):
+        self._lines.delete_i([self._row])
diff --git a/src/View/REPLines/Window.py b/src/View/REPLines/Window.py
index 92cb015ba47090a8f1a3d7b8091810a314973182..a7bc4ce0b29f9b1dec6d9a0052797c96dd57febc 100644
--- a/src/View/REPLines/Window.py
+++ b/src/View/REPLines/Window.py
@@ -55,6 +55,7 @@ class REPLineListWindow(PamhyrWindow):
         self._list = ListModel(
             list_view=lst,
             data=self._study.river.rep_lines,
+            undo=self._undo_stack,
         )
 
     def setup_connections(self):
@@ -80,7 +81,10 @@ class REPLineListWindow(PamhyrWindow):
 
     def delete(self):
         rows = self.selected_rows()
-        self._list.delete(rows)
+        if len(rows) < 0:
+            return
+
+        self._list.delete(rows[0])
 
     def edit(self):
         rows = self.selected_rows()
@@ -99,6 +103,15 @@ class REPLineListWindow(PamhyrWindow):
                 config=self._config,
                 rep_line=rep_line,
                 trad=self._trad,
+                undo=self._undo_stack,
                 parent=self,
             )
             win.show()
+
+    def _undo(self):
+        self._undo_stack.undo()
+        self.update()
+
+    def _redo(self):
+        self._undo_stack.redo()
+        self.update()
diff --git a/src/View/Tools/PamhyrList.py b/src/View/Tools/PamhyrList.py
index 7acc7ee066dde1a78b24b9d8fc5fe78338f71163..709672b067569f90d985253f544136bba69af17f 100644
--- a/src/View/Tools/PamhyrList.py
+++ b/src/View/Tools/PamhyrList.py
@@ -73,11 +73,11 @@ class PamhyrListModel(QAbstractListModel):
 
     def undo(self):
         self._undo.undo()
-        self.layoutChanged.emit()
+        self.update()
 
     def redo(self):
         self._undo.redo()
-        self.layoutChanged.emit()
+        self.update()
 
     def update(self):
         self.layoutChanged.emit()