Repository: 12rambau/sepal_ui

Issue:
NumberField is not using fontawesome icons

Discussion:

Patch:
diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml
index 88dda404..e29b052d 100644
--- a/.github/workflows/unit.yml
+++ b/.github/workflows/unit.yml
@@ -60,15 +60,7 @@ jobs:
 
       - name: Check that there are no unexpected Sphinx warnings
         if: matrix.python-version == '3.8'
-        shell: python
-        run: |
-          from pathlib import Path
-          text = Path("warnings.txt").read_text().strip()
-          print("\n=== Sphinx Warnings ===\n\n" + text)  # Print just for reference so we can look at the logs
-          warnings = [ii for ii in text.split("\n")]
-          expected = ["transition", "CHANGELOG.md", "modules.rst", "target"]
-          unexpected = [ii for ii in text.split("\n") if all(i not in ii for i in expected)]
-          assert len(unexpected) == 0
+        run: python tests/check_warnings.py
 
       - name: test with pytest
         run: pytest --color=yes --cov --cov-report=xml tests
diff --git a/docs/source/tutorials/add-tile.rst b/docs/source/tutorials/add-tile.rst
index 91524bc2..f63948a3 100644
--- a/docs/source/tutorials/add-tile.rst
+++ b/docs/source/tutorials/add-tile.rst
@@ -15,6 +15,7 @@ the tile cod is the following :
     import ipyvuetify as v
     from component.message import ms
     from sepal_ui.scripts import utils as su
+    from sepal_ui.scripts import decorator as sd
 
     class MyTile(sw.Tile):
     
@@ -45,7 +46,7 @@ the tile cod is the following :
         # now that the Tile is created we can link it to a specific function
         self.btn.on_event('click', self._on_run)
     
-    @su.loading_button(debug=False)
+    @sd.loading_button(debug=False)
     def _on_run(self, widget, data, event): 
 
         time.sleep(5)
diff --git a/docs/source/tutorials/decorator.rst b/docs/source/tutorials/decorator.rst
index 79f8e5c1..632068e0 100644
--- a/docs/source/tutorials/decorator.rst
+++ b/docs/source/tutorials/decorator.rst
@@ -82,6 +82,7 @@ Let's import the required modules. All the decorators are stored in the utils mo
     import ipyvuetify as v
     import sepal_ui.sepalwidgets as sw
     import sepal_ui.scripts.utils as su
+    import sepal_ui.scripts.decorator as sd
     
 
 Now, create a custom tile with all the elements that we will require to be displayed in our interface, as well as the events that we want to trigger.
@@ -125,19 +126,19 @@ It's time to use the decorators in the class methods. For this example, we will
 
 .. code-block:: python
 
-        @su.loading_button()
-        @su.switch('loading', 'disabled', on_widgets=['w_select'])
+        @sd.loading_button()
+        @sd.switch('loading', 'disabled', on_widgets=['w_select'])
         def get_items_event(self):
             """request GEE items"""
     
             self.children = self.request_items()
         
-        @su.switch('loading', 'disabled')
+        @sd.switch('loading', 'disabled')
         def on_card_event(self):
             
             sleep(2)
         
-        @su.need_ee
+        @sd.need_ee
         def request_items(self):
             """Connect to gee and request the root assets id's"""
             
diff --git a/noxfile.py b/noxfile.py
index 76b3ac6b..14582d70 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -27,7 +27,10 @@ def docs(session):
         "docs/source/modules",
         "./sepal_ui",
     )
-    session.run("sphinx-build", "-v", "-b", "html", "docs/source", "build")
+    session.run(
+        "sphinx-build", "-v", "-b", "html", "docs/source", "build", "-w", "warnings.txt"
+    )
+    session.run("python", "tests/check_warnings.py")
 
 
 @nox.session(name="docs-live", reuse_venv=False)
diff --git a/sepal_ui/aoi/aoi_model.py b/sepal_ui/aoi/aoi_model.py
index 582abd49..28e00c8a 100644
--- a/sepal_ui/aoi/aoi_model.py
+++ b/sepal_ui/aoi/aoi_model.py
@@ -8,12 +8,10 @@ import ee
 import geopandas as gpd
 import pandas as pd
 import traitlets as t
-from deprecated.sphinx import deprecated
 from ipyleaflet import GeoJSON
 from typing_extensions import Self
 
 from sepal_ui import color
-from sepal_ui import sepalwidgets as sw
 from sepal_ui.frontend import styles as ss
 from sepal_ui.message import ms
 from sepal_ui.model import Model
@@ -142,13 +140,8 @@ class AoiModel(Model):
     ipygeojson: Optional[GeoJSON] = None
     "The representation of the AOI as a ipyleaflet layer"
 
-    @deprecated(
-        version="2.11.3",
-        reason=":code:`alert` positional argument will be removed. Successfull output messages has to be created in AoiView.",
-    )
     def __init__(
         self,
-        alert: Optional[sw.Alert] = None,
         gee: bool = True,
         vector: Optional[Union[str, Path]] = None,
         asset: Optional[Union[str, Path]] = None,
diff --git a/sepal_ui/frontend/js/jupyter_clip.js b/sepal_ui/frontend/js/jupyter_clip.js
new file mode 100644
index 00000000..02605b66
--- /dev/null
+++ b/sepal_ui/frontend/js/jupyter_clip.js
@@ -0,0 +1,7 @@
+var tempInput = document.createElement("input");
+tempInput.value = _txt;
+document.body.appendChild(tempInput);
+tempInput.focus();
+tempInput.select();
+document.execCommand("copy");
+document.body.removeChild(tempInput);
diff --git a/sepal_ui/message/en/password_field.json b/sepal_ui/message/en/password_field.json
new file mode 100644
index 00000000..2f182d19
--- /dev/null
+++ b/sepal_ui/message/en/password_field.json
@@ -0,0 +1,5 @@
+{
+  "password_field": {
+    "label": "Password"
+  }
+}
diff --git a/sepal_ui/reclassify/parameters.py b/sepal_ui/reclassify/parameters.py
index bb214e6f..36e36c83 100644
--- a/sepal_ui/reclassify/parameters.py
+++ b/sepal_ui/reclassify/parameters.py
@@ -2,13 +2,13 @@ from sepal_ui.message import ms
 
 __all__ = ["MATRIX_NAMES", "TABLE_NAMES", "NO_VALUE", "SCHEMA"]
 
-MATRIX_NAMES = ["src", "dst"]
-TABLE_NAMES = ["code", "desc", "color"]
+MATRIX_NAMES: list = ["src", "dst"]
+TABLE_NAMES: list = ["code", "desc", "color"]
 
 # Set a value for missing reclassifications
-NO_VALUE = 999
+NO_VALUE: int = 999
 
-SCHEMA = {
+SCHEMA: dict = {
     "id": [ms.rec.table.schema.id, "number"],
     "code": [ms.rec.table.schema.code, "number"],
     "desc": [ms.rec.table.schema.description, "string"],
diff --git a/sepal_ui/reclassify/reclassify_model.py b/sepal_ui/reclassify/reclassify_model.py
index bebf3baa..a3a15ff2 100644
--- a/sepal_ui/reclassify/reclassify_model.py
+++ b/sepal_ui/reclassify/reclassify_model.py
@@ -1,15 +1,18 @@
 from pathlib import Path
+from typing import Any, Optional, Union
 
 import ee
 import geopandas as gpd
 import numpy as np
 import pandas as pd
 import rasterio as rio
+import traitlets as t
 from matplotlib.colors import to_rgba
 from natsort import natsorted
 from rasterio.windows import from_bounds
-from traitlets import Any, Bool, Dict, Int
+from typing_extensions import Self
 
+from sepal_ui import aoi
 from sepal_ui.message import ms
 from sepal_ui.model import Model
 from sepal_ui.scripts import decorator as sd
@@ -23,91 +26,89 @@ __all__ = ["ReclassifyModel"]
 
 class ReclassifyModel(Model):
 
-    # inputs
-    # should be unicode but we need to handle when nothing is set (None)
-    band = Any(None).tag(sync=True)
-    "str|int: the band name or number to use for the reclassification if raster type. Use property name if vector type"
+    band: t.Any = t.Any(None).tag(sync=True)
+    "the band name or number to use for the reclassification if raster type. Use property name if vector type"
 
-    src_local = Any(None).tag(sync=True)
-    "str: the source file to reclassify (from a local path) only used if :code:`gee=False`"
+    src_local: t.Any = t.Any(None).tag(sync=True)
+    "the source file to reclassify (from a local path) only used if :code:`gee=False`"
 
-    src_gee = Any(None).tag(sync=True)
-    "str: AssetId of the used input asset for reclassification. Only used if :code:`gee=True`"
+    src_gee: t.Unicode = t.Unicode(None, allow_none=True).tag(sync=True)
+    "AssetId of the used input asset for reclassification. Only used if :code:`gee=True`"
 
-    dst_class_file = Any(None).tag(sync=True)
-    "str: the destination file for reclassify matrix"
+    dst_class_file: t.Any = t.Any(None).tag(sync=True)
+    "the destination file for reclassify matrix"
 
-    dst_dir = Any(None).tag(sync=True)
-    "str: the dir used to store the output"
+    dst_dir: Union[Path, str] = ""
+    "the dir used to store the output"
 
-    gee = Bool(False).tag(sync=True)
-    "bool: either to use the gee backend or not"
+    gee: t.Bool = t.Bool(False).tag(sync=True)
+    "either to use the gee backend or not"
 
-    aoi_model = None
-    "aoi.AoiModel: AOI model object to get an area of interest if one is selected"
+    aoi_model: Optional[aoi.AoiModel] = None
+    "AOI model object to get an area of interest if one is selected"
 
-    folder = None
-    "str: the init GEE asset folder where the asset selector should start looking (debugging purpose)"
+    folder: str = ""
+    "the init GEE asset folder where the asset selector should start looking (debugging purpose)"
 
-    enforce_aoi = None
-    "bool: either or not an aoi should be set to allow the reclassification"
+    enforce_aoi: bool = False
+    "either or not an aoi should be set to allow the reclassification"
 
     # data manipulation
-    matrix = Dict({}).tag(sync=True)
-    "dict: the transfer matrix between the input and the output using the following format: {old_value: new_value, ...}"
+    matrix: t.Dict = t.Dict({}).tag(sync=True)
+    "the transfer matrix between the input and the output using the following format: {old_value: new_value, ...}"
 
     # outputs
-    input_type = Bool(False).tag(sync=True)  # 1 raster, 0 vector
-    "bool: the input type, 1 for raster and 0 for vector"
+    input_type: t.Bool = t.Bool(False).tag(sync=True)  # 1 raster, 0 vector
+    "the input type, 1 for raster and 0 for vector"
 
-    src_class = Dict({}).tag(sync=True)
-    "dict: the source classes using the following columns: {code: (desc, color)}"
+    src_class: t.Dict = t.Dict({}).tag(sync=True)
+    "the source classes using the following columns: {code: (desc, color)}"
 
-    dst_class = Dict({}).tag(sync=True)
-    "dict: the destination classes using the following columns: {code: (desc, color)}"
+    dst_class: t.Dict = t.Dict({}).tag(sync=True)
+    "the destination classes using the following columns: {code: (desc, color)}"
 
-    dst_local = Any(None).tag(sync=True)
-    "str: the output file. default to :code:`dst_dir/f'{src_local.stem}_reclass.{src_local.suffix}'`"
+    dst_local: t.Unicode = t.Unicode(None, allow_none=True).tag(sync=True)
+    "the output file. default to :code:`dst_dir/f'{src_local.stem}_reclass.{src_local.suffix}'`"
 
-    dst_gee = Any(None).tag(sync=True)
-    "str: the output assetId. default to :code:`dst_dir/f'{src_gee.stem}_reclass'`"
+    dst_gee: t.Unicode = t.Unicode(None, allow_none=True).tag(sync=True)
+    "the output assetId. default to :code:`dst_dir/f'{src_gee.stem}_reclass'`"
 
     # Create a state var, to determine if an asset has been remaped
-    remaped = Int(False).tag(sync=True)
-    "int: state var updated each time an input is remapped"
+    remaped: t.Int = t.Int(0).tag(sync=True)
+    "state var updated each time an input is remapped"
 
-    save = False
-    "bool: either or not the relcassified dataset need to be saved"
+    save: bool = False
+    "either or not the relcassified dataset need to be saved"
 
-    dst_local_memory = None
-    "Any: the local output of the reclassification"
+    dst_local_memory: Any = None
+    "the local output of the reclassification"
 
-    dst_gee_memory = None
-    "Any: the gee output of the reclassification"
+    dst_gee_memory: Any = None
+    "the gee output of the reclassification"
 
-    table_created = Bool(False).tag(sync=True)
-    "bool: either or not a table have been created"
+    table_created: t.Bool = t.Bool(False).tag(sync=True)
+    "either or not a table have been created"
 
     def __init__(
         self,
-        gee=False,
-        dst_dir=Path.home(),
-        aoi_model=None,
-        folder="",
-        save=True,
-        enforce_aoi=False,
+        gee: bool = False,
+        dst_dir: Union[Path, str] = Path.home(),
+        aoi_model: Optional[aoi.AoiModel] = None,
+        folder: str = "",
+        save: bool = True,
+        enforce_aoi: bool = False,
         **kwargs,
-    ):
+    ) -> None:
 
         """
         Reclassification model to store information about the current reclassification and share them within your app. save all the input and output of the reclassification + the the matrix to move from one to another. It is embeding 2 backends, one based on GEE that will use assets as in/out and another based on python that will use local files as in/out. The model can handle both vector and raster data, the format and name of the output will be determined from the the input format/name. The developer will still have the possiblity to choose where to save the outputs (folder name).
 
         Args:
-            gee (bool): either or not to set :code:`gee` to True
-            dst_dir (str): the destination forlder for outputs
-            folder(str, optional): the init GEE asset folder where the asset selector should start looking (debugging purpose)
-            aoi_model (aoi.AoiModel, optional): the aoi model to link to the reclassify workflow
-            enforce_aoi (bool, optional): either or not an aoi should be set to allow the reclassification
+            gee: either or not to set :code:`gee` to True
+            dst_dir: the destination forlder for outputs
+            folder: the init GEE asset folder where the asset selector should start looking (debugging purpose)
+            aoi_model: the aoi model to link to the reclassify workflow
+            enforce_aoi: either or not an aoi should be set to allow the reclassification
         """
 
         # init the model
@@ -143,16 +144,12 @@ class ReclassifyModel(Model):
         # set if the model need to save by default
         self.save = save
 
-    def get_classes(self):
+    def get_classes(self) -> dict:
         """
         Extract the classes from the class file. The class file need to be compatible with the reclassify tool i.e. a table file with 3 headerless columns using the following format: 'code', 'desc', 'color'. Color need to be set in hexadecimal to be read else black will be used.
 
-        Args:
-            file (pathlike object): the pathlib object of the class file
-
         Return:
-            (dict): the dict of the classes using following format:
-                {code: (name, color)}
+            the dict of the classes using following format: {code: (name, color)}
         """
 
         file = self.dst_class_file
@@ -176,12 +173,12 @@ class ReclassifyModel(Model):
         # create a dict out of it
         return class_list
 
-    def get_type(self):
+    def get_type(self) -> bool:
         """
         Guess the type of the input and set the input type attribute for the model (vector or raster)
 
         Return:
-            (bool): the type of input (1 for raster, 0 for vector)
+            the type of input (1 for raster, 0 for vector)
         """
 
         if self.gee:
@@ -210,7 +207,7 @@ class ReclassifyModel(Model):
                 raise AttributeError(f"Unrecognized file format: {input_path.suffix}")
         return self.input_type
 
-    def get_bands(self):
+    def get_bands(self) -> list:
         """
         Use the input_type to extract all the bands/properties from the input
 
@@ -255,8 +252,13 @@ class ReclassifyModel(Model):
         # remember to use self as a parameter
         return natsorted(band_func[self.gee][self.input_type]())
 
-    def get_aoi(self):
-        """Validate and get feature collection from aoi_model"""
+    def get_aoi(self) -> Union[gpd.GeoDataFrame, ee.ComputedObject]:
+        """
+        Validate and get feature collection from aoi_model
+
+        Returns:
+            the saved AOI in the appropriate format
+        """
 
         # by default it's none
         aoi = None
@@ -279,14 +281,13 @@ class ReclassifyModel(Model):
 
         return aoi
 
-    def unique(self):
+    def unique(self) -> dict:
         """
         Retreive all the existing class from the specified band/property according to the input_type.
         The data will be saved in self.src_class with no_name and black as a color.
 
         Return:
-            (Dict): the unique class value found in the specified band/property
-            and there color/name defaulted to none and black
+            the unique class value found in the specified band/property and there color/name defaulted to none and black
         """
 
         if not self.band:
@@ -359,12 +360,9 @@ class ReclassifyModel(Model):
 
         return self.src_class
 
-    def reclassify(self):
+    def reclassify(self) -> Self:
         """
         Reclassify the input according to the provided matrix. For vector file type reclassifying correspond to add an extra column at the end, for raster the initial class band will be replaced by the new class, the oher being kept unmodified. vizualization colors will be set for both local (QGIS compatible) and assets (SEPAL vizualization compatible).
-
-        Return:
-            self
         """
 
         if not self.matrix:
@@ -586,12 +584,12 @@ class ReclassifyModel(Model):
 
         return "Asset successfully reclassified."
 
-    def set_dst_gee(self):
+    def set_dst_gee(self) -> str:
         """
         Creates a unique and consecutive asset name based on the source
 
         Return:
-            (str) the destination folder
+            the destination folder
         """
 
         # create the asset_id
diff --git a/sepal_ui/reclassify/reclassify_tile.py b/sepal_ui/reclassify/reclassify_tile.py
index 48adc351..4d6ac680 100644
--- a/sepal_ui/reclassify/reclassify_tile.py
+++ b/sepal_ui/reclassify/reclassify_tile.py
@@ -1,48 +1,53 @@
 from pathlib import Path
+from typing import Optional, Union
 
 import ipyvuetify as v
 from traitlets import link
 
-from sepal_ui import reclassify as rec
+from sepal_ui import aoi
 from sepal_ui import sepalwidgets as sw
 from sepal_ui.message import ms
 
+from .reclassify_model import ReclassifyModel
+from .reclassify_view import ReclassifyView
+from .table_view import TableView
+
 __all__ = ["ReclassifyTile"]
 
 
 class ReclassifyTile(sw.Tile):
 
-    result_dir = None
-    "pathlib.Path: Directory to store the outputs (rasters, and csv_files)."
+    result_dir: Union[Path, str] = ""
+    "Directory to store the outputs (rasters, and csv_files)."
 
-    model = None
-    "ReclassifyModel: the reclassify model to use with these inputs"
+    model: Optional[ReclassifyModel] = None
+    "the reclassify model to use with these inputs"
 
-    reclassify_view = None
-    "ReclassifyView: a fully qualified ReclassifyView object"
+    reclassify_view: Optional[ReclassifyView] = None
+    "a fully qualified ReclassifyView object"
 
-    table_view = None
-    "TableView: a fully qualified TableView object"
+    table_view: Optional[TableView] = None
+    "a fully qualified TableView object"
 
     def __init__(
         self,
-        results_dir=Path.home() / "downloads",
-        gee=True,
-        dst_class=None,
-        default_class={},
-        aoi_model=None,
-        folder="",
+        results_dir: Union[Path, str] = Path.home() / "downloads",
+        gee: bool = True,
+        dst_class: Union[Path, str] = "",
+        default_class: dict = {},
+        aoi_model: Optional[aoi.AoiModel] = None,
+        folder: str = "",
         **kwargs
-    ):
+    ) -> None:
         """
         All in one tile to reclassify GEE assets or local raster and create custom classifications
 
         Args:
-            results_dir (str|pathlike object): Directory to store the outputs (rasters, and csv_files). default to ~/downloads
-            gee (bool): Use GEE variant, to reclassify assets or local input. default True
-            dst_class (str|pathlib.Path, optional): the file to be used as destination classification. for app that require specific code system the file can be set prior and the user won't have the oportunity to change it
-            default_class (dict|optional): the default classification system to use, need to point to existing sytem: {name: absolute_path}
-            folder(str, optional): the init GEE asset folder where the asset selector should start looking (debugging purpose)
+            results_dir: Directory to store the outputs (rasters, and csv_files). default to ~/downloads
+            gee: Use GEE variant, to reclassify assets or local input. default True
+            dst_class: the file to be used as destination classification. for app that require specific code system the file can be set prior and the user won't have the oportunity to change it
+            default_class: the default classification system to use, need to point to existing sytem: {name: absolute_path}
+            folder: the init GEE asset folder where the asset selector should start looking (debugging purpose)
         """
 
         # output directory
@@ -50,12 +55,12 @@ class ReclassifyTile(sw.Tile):
 
         self.aoi_model = aoi_model
         # create the model
-        self.model = rec.ReclassifyModel(
+        self.model = ReclassifyModel(
             dst_dir=self.results_dir, gee=gee, aoi_model=self.aoi_model, folder=folder
         )
 
         # set the tabs elements
-        self.reclassify_view = rec.ReclassifyView(
+        self.reclassify_view = ReclassifyView(
             self.model,
             out_path=self.results_dir,
             gee=gee,
@@ -65,7 +70,7 @@ class ReclassifyTile(sw.Tile):
             folder=folder,
         ).nest_tile()
 
-        self.table_view = rec.TableView(out_path=self.results_dir).nest_tile()
+        self.table_view = TableView(out_path=self.results_dir).nest_tile()
 
         # create the tab
         tiles = [self.reclassify_view, self.table_view]
diff --git a/sepal_ui/reclassify/reclassify_view.py b/sepal_ui/reclassify/reclassify_view.py
index 5d8b1daf..5a7ec6bd 100644
--- a/sepal_ui/reclassify/reclassify_view.py
+++ b/sepal_ui/reclassify/reclassify_view.py
@@ -1,10 +1,13 @@
 from pathlib import Path
+from typing import Optional, Union
 
 import ipyvuetify as v
 import pandas as pd
 from traitlets import Unicode
+from typing_extensions import Self
 
 import sepal_ui.sepalwidgets as sw
+from sepal_ui import aoi
 from sepal_ui.message import ms
 from sepal_ui.scripts import decorator as sd
 from sepal_ui.scripts import utils as su
@@ -17,15 +20,15 @@ __all__ = ["ReclassifyView"]
 
 class ImportMatrixDialog(v.Dialog):
 
-    file = Unicode("").tag(sync=True)
+    file: Unicode = Unicode("").tag(sync=True)
     "the file to use"
 
-    def __init__(self, folder, **kwargs):
+    def __init__(self, folder: Union[str, Path], **kwargs) -> None:
         """
         Dialog to select the file to use and fill the matrix
 
         Args:
-            folder (pathlike object): the path to the saved classifications
+            folder: the path to the saved classifications
         """
 
         # create the 3 widgets
@@ -48,14 +51,14 @@ class ImportMatrixDialog(v.Dialog):
         # js behaviour
         cancel.on_event("click", self._cancel)
 
-    def _cancel(self, widget, event, data):
+    def _cancel(self, *args) -> Self:
         """exit and do nothing"""
 
         self.value = False
 
         return self
 
-    def show(self):
+    def show(self) -> Self:
 
         self.value = True
 
@@ -63,14 +66,13 @@ class ImportMatrixDialog(v.Dialog):
 
 
 class SaveMatrixDialog(v.Dialog):
-    """
-    Dialog to setup the name of the output matrix file
-
-    Args:
-        folder (pathlike object): the path to the save folder. default to ~/
-    """
+    def __init__(self, folder: Union[Path, str] = Path.home(), **kwargs) -> None:
+        """
+        Dialog to setup the name of the output matrix file
 
-    def __init__(self, folder=Path.home(), **kwargs):
+        Args:
+            folder: the path to the save folder. default to ~/
+        """
 
         # save the matrix
         self._matrix = {}
@@ -102,7 +104,7 @@ class SaveMatrixDialog(v.Dialog):
         self.w_file.on_event("blur", self._sanitize)
         self.w_file.observe(self._store_info, "v_model")
 
-    def _store_info(self, change):
+    def _store_info(self, change: dict) -> None:
         """Display where will be the file written"""
 
         new_val = change["new"]
@@ -115,7 +117,9 @@ class SaveMatrixDialog(v.Dialog):
 
         self.alert.add_msg(msg)
 
-    def _cancel(self, widget, event, data):
+        return
+
+    def _cancel(self, *args) -> Self:
         """do nothing and exit"""
 
         self.w_file.v_model = None
@@ -123,7 +127,7 @@ class SaveMatrixDialog(v.Dialog):
 
         return self
 
-    def _save(self, widget, event, data):
+    def _save(self, *args) -> Self:
         """save the matrix in a specified file"""
 
         file = self.folder / f"{su.normalize_str(self.w_file.v_model)}.csv"
@@ -137,8 +141,10 @@ class SaveMatrixDialog(v.Dialog):
 
         return self
 
-    def show(self, matrix):
-        """show the dialog and set the matrix values"""
+    def show(self, matrix: dict) -> Self:
+        """
+        show the dialog and set the matrix values
+        """
 
         self._matrix = matrix
 
@@ -149,8 +155,10 @@ class SaveMatrixDialog(v.Dialog):
 
         return self
 
-    def _sanitize(self, widget, event, data):
-        """sanitize the used name when saving"""
+    def _sanitize(self, *args) -> Self:
+        """
+        sanitize the used name when saving
+        """
 
         if not self.w_file.v_model:
             return self
@@ -161,15 +169,14 @@ class SaveMatrixDialog(v.Dialog):
 
 
 class ClassSelect(sw.Select):
-    """
-    Custom widget to pick the value of a original class in the new classification system
-
-    Args:
-        new_codes(dict): the dict of the new codes to use as items {code: (name, color)}
-        code (int): the orginal code of the class
-    """
+    def __init__(self, new_codes: dict, old_code: int, **kwargs) -> None:
+        """
+        Custom widget to pick the value of a original class in the new classification system
 
-    def __init__(self, new_codes, old_code, **kwargs):
+        Args:
+            new_codes: the dict of the new codes to use as items {code: (name, color)}
+            code: the orginal code of the class
+        """
 
         # set default parameters
         self.items = [
@@ -188,25 +195,28 @@ class ClassSelect(sw.Select):
 
 
 class ReclassifyTable(sw.SimpleTable):
-    """
-    Table to store the reclassifying information.
-    2 columns are integrated, the new class value and the values in the original input
-    One can select multiple class to be reclassify in the new classification
 
-    Args:
-        model (ReclassifyModel): model embeding the traitlet dict to store the reclassifying matrix. keys: class value in dst, values: list of values in src.
-        dst_classes (dict|optional): a dictionnary that represent the classes of new the new classification table as {class_code: (class_name, class_color)}. class_code must be ints and class_name str.
-        src_classes (dict|optional): the list of existing values within the input file {class_code: (class_name, class_color)}
+    HEADERS: list = ms.rec.rec.headers
+    "name of the column header [from, to]"
 
-    Attributes:
-        HEADER (list): name of the column header (from, to)
-        model (ReclassifyModel): the reclassifyModel object to manipulate the
-            input file and save parameters
-    """
+    model: Optional[ReclassifyModel] = None
+    "the reclassifyModel object to manipulate the input file and save parameters"
 
-    HEADERS = ms.rec.rec.headers
+    def __init__(
+        self,
+        model: ReclassifyModel,
+        dst_classes: dict = {},
+        src_classes: dict = {},
+        **kwargs,
+    ) -> None:
+        """
+        Table to store the reclassifying information. 2 columns are integrated, the new class value and the values in the original input. One can select multiple class to be reclassify in the new classification
 
-    def __init__(self, model, dst_classes={}, src_classes={}, **kwargs):
+        Args:
+            model (ReclassifyModel): model embeding the traitlet dict to store the reclassifying matrix. keys: class value in dst, values: list of values in src.
+            dst_classes (dict|optional): a dictionnary that represent the classes of new the new classification table as {class_code: (class_name, class_color)}. class_code must be ints and class_name str.
+            src_classes (dict|optional): the list of existing values within the input file {class_code: (class_name, class_color)}
+        """
 
         # default parameters
         self.dense = True
@@ -226,13 +236,13 @@ class ReclassifyTable(sw.SimpleTable):
         ]
         self.set_table(dst_classes, src_classes)
 
-    def set_table(self, dst_classes, src_classes):
+    def set_table(self, dst_classes: dict, src_classes: dict) -> Self:
         """
         Rebuild the table content based on the new_classes and codes provided
 
         Args:
-            dst_classes (dict|optional): a dictionnary that represent the classes of new the new classification table as {class_code: (class_name, class_color)}. class_code must be ints and class_name str.
-            src_classes (dict|optional): the list of existing values within the input file {class_code: (class_name, class_color)}
+            dst_classes: a dictionnary that represent the classes of new the new classification table as {class_code: (class_name, class_color)}. class_code must be ints and class_name str.
+            src_classes: the list of existing values within the input file {class_code: (class_name, class_color)}
 
         Return:
             self
@@ -282,8 +292,10 @@ class ReclassifyTable(sw.SimpleTable):
 
         return self
 
-    def _update_matrix_values(self, change):
-        """Update the appropriate matrix value when a Combo select change"""
+    def _update_matrix_values(self, change: dict) -> Self:
+        """
+        Update the appropriate matrix value when a Combo select change
+        """
 
         # get the code of the class in the src classification
         code = change["owner"]._metadata["class"]
@@ -295,24 +307,6 @@ class ReclassifyTable(sw.SimpleTable):
 
 
 class ReclassifyView(sw.Card):
-    """
-    Stand-alone Card object allowing the user to reclassify a input file. the input can be of any type (vector or raster) and from any source (local or GEE).
-    The user need to provide a destination classification file (table) in the following format : 3 headless columns: 'code', 'desc', 'color'. Once all the old class have been attributed to their new class the file can be exported in the source format to local memory or GEE. the output is also savec in memory for further use in the app. It can be used as a tile in a sepal_ui app. The id\\_ of the tile is set to "reclassify_tile"
-
-    Args:
-        model (ReclassifyModel): the reclassify model to manipulate the
-            classification dataset. default to a new one
-        class_path (str,optional): Folder path containing already existing
-            classes. Default to ~/
-        out_path (str,optional): the folder to save the created classifications.
-            default to ~/downloads
-        gee (bool): either or not to set :code:`gee` to True. default to False
-        dst_class (str|pathlib.Path, optional): the file to be used as destination classification. for app that require specific code system the file can be set prior and the user won't have the oportunity to change it
-        default_class (dict|optional): the default classification system to use, need to point to existing sytem: {name: absolute_path}
-        folder(str, optional): the init GEE asset folder where the asset selector should start looking (debugging purpose)
-        save (bool, optional): Whether to write/export the result or not.
-        enforce_aoi (bool, optional): either or not an aoi should be set to allow the reclassification
-    """
 
     MAX_CLASS = 20
     "int: the number of line in the table to trigger the display of an extra toolbar and alert"
@@ -355,18 +349,33 @@ class ReclassifyView(sw.Card):
 
     def __init__(
         self,
-        model=None,
-        class_path=Path.home(),
-        out_path=Path.home() / "downloads",
-        gee=False,
-        dst_class=None,
-        default_class={},
-        aoi_model=None,
-        save=True,
-        folder="",
-        enforce_aoi=False,
+        model: Optional[ReclassifyModel] = None,
+        class_path: Union[str, Path] = Path.home(),
+        out_path: Union[str, Path] = Path.home() / "downloads",
+        gee: bool = False,
+        dst_class: Union[str, Path] = "",
+        default_class: dict = {},
+        aoi_model: Optional[aoi.AoiModel] = None,
+        save: bool = True,
+        folder: Union[str, Path] = "",
+        enforce_aoi: bool = False,
         **kwargs,
-    ):
+    ) -> None:
+        """
+        Stand-alone Card object allowing the user to reclassify a input file. the input can be of any type (vector or raster) and from any source (local or GEE).
+        The user need to provide a destination classification file (table) in the following format : 3 headless columns: 'code', 'desc', 'color'. Once all the old class have been attributed to their new class the file can be exported in the source format to local memory or GEE. the output is also savec in memory for further use in the app. It can be used as a tile in a sepal_ui app. The id\\_ of the tile is set to "reclassify_tile"
+
+        Args:
+            model: the reclassify model to manipulate the classification dataset. default to a new one
+            class_path: Folder path containing already existing classes. Default to ~/
+            out_path: the folder to save the created classifications. default to ~/downloads
+            gee: either or not to set :code:`gee` to True. default to False
+            dst_class: the file to be used as destination classification. for app that require specific code system the file can be set prior and the user won't have the oportunity to change it
+            default_class: the default classification system to use, need to point to existing sytem: {name: absolute_path}
+            folder: the init GEE asset folder where the asset selector should start looking (debugging purpose)
+            save: Whether to write/export the result or not.
+            enforce_aoi: either or not an aoi should be set to allow the reclassification
+        """
 
         # create metadata to make it compatible with the framwork app system
         self._metadata = {"mount_id": "reclassify_tile"}
@@ -581,8 +590,10 @@ class ReclassifyView(sw.Card):
         self.reclassify_btn.on_event("click", self.reclassify)
         [btn.on_event("click", self._set_dst_class_file) for btn in self.btn_list]
 
-    def _set_dst_class_file(self, widget, event, data):
-        """Set the destination classification according to the one selected with btn. alter the widgets properties to reflect this change"""
+    def _set_dst_class_file(self, widget: v.VuetifyWidget, *args) -> Self:
+        """
+        Set the destination classification according to the one selected with btn. alter the widgets properties to reflect this change
+        """
 
         # get the filename
         filename = widget._metadata["path"]
@@ -599,12 +610,9 @@ class ReclassifyView(sw.Card):
 
         return self
 
-    def load_matrix_content(self, widget, event, data):
+    def load_matrix_content(self, widget: v.VuetifyWidget, *args) -> Self:
         """
         Load the content of the file in the matrix. The table need to be already set to perform this operation
-
-        Return:
-            self
         """
         self.import_dialog.value = False
         file = self.import_dialog.w_file.v_model
@@ -656,13 +664,10 @@ class ReclassifyView(sw.Card):
 
         return self
 
-    def reclassify(self, widget, event, data):
+    def reclassify(self, *args) -> Self:
         """
         Reclassify the input and store it in the appropriate format.
         The input is not saved locally to avoid memory overload.
-
-        Return:
-            self
         """
 
         # create the output file
@@ -674,8 +679,10 @@ class ReclassifyView(sw.Card):
         return self
 
     @sd.switch("loading", "disabled", on_widgets=["w_code"])
-    def _update_band(self, change):
-        """Update the band possibility to the available bands/properties of the input"""
+    def _update_band(self, *args) -> Self:
+        """
+        Update the band possibility to the available bands/properties of the input
+        """
 
         # guess the file type and save it in the model
         self.model.get_type()
@@ -687,13 +694,10 @@ class ReclassifyView(sw.Card):
 
     @sd.switch("disabled", on_widgets=["reclassify_btn"], targets=[False])
     @sd.switch("table_created", on_widgets=["model"], targets=[True])
-    def get_reclassify_table(self, widget, event, data):
+    def get_reclassify_table(self, *args) -> Self:
         """
         Display a reclassify table which will lead the user to select
         a local code 'from user' to a target code based on a classes file
-
-        Return:
-            self
         """
 
         # get the destination classes
@@ -716,14 +720,11 @@ class ReclassifyView(sw.Card):
 
         return self
 
-    def nest_tile(self):
+    def nest_tile(self) -> Self:
         """
         Prepare the view to be used as a nested component in a tile.
         the elevation will be set to 0 and the title remove from children.
         The mount_id will also be changed to nested
-
-        Return:
-            self
         """
 
         # remove id
diff --git a/sepal_ui/reclassify/table_view.py b/sepal_ui/reclassify/table_view.py
index ea708156..d8272b13 100644
--- a/sepal_ui/reclassify/table_view.py
+++ b/sepal_ui/reclassify/table_view.py
@@ -1,10 +1,12 @@
 from colorsys import rgb_to_hls, rgb_to_hsv
 from pathlib import Path
+from typing import Optional, Union
 
 import ipyvuetify as v
 import pandas as pd
 from matplotlib.colors import to_rgb
 from traitlets import Int
+from typing_extensions import Self
 
 from sepal_ui import sepalwidgets as sw
 from sepal_ui.message import ms
@@ -19,22 +21,14 @@ class ClassTable(sw.DataTable):
 
     SCHEMA = param.SCHEMA
 
-    def __init__(self, out_path=Path.home() / "downloads", **kwargs):
+    def __init__(
+        self, out_path: Union[str, Path] = Path.home() / "downloads", **kwargs
+    ) -> None:
         """
         Custom data table to modify, display and save classification. From this interface, a user can modify a classification starting from a scratch or by loading a classification file. the display datatable allow all the CRUD fonctionality (create, read, update, delete).
 
-        Attributes:
-            SCHEMA (const list): the schema of the classification: ['id', 'code', 'description', 'color']
-            out_path (pathlib.Path): the output folder to save the created classifications
-            save_dialog (v.Dialog): the Dialog to save the classification (select name and format)
-            edit_dialog (v.Dialog): the dialog to edit/create a noew line in the data-table. The user will provide: the code, the description and the color (with a color selector)
-            edit_icon (v.Icon): the btn to open the edit_dialog (a line need to be selected in the table)
-            delete_icon (v.icon): the btn to delete a selected line in the table
-            add_icon (v.Icon): the btn to add a new line in the table
-            save_icon (v.Icon): the btn to open the save_dialog and save the current classification table
-
         Args:
-            out_path (str|optional): output path where table will be saved, default to ~/downloads/
+            out_path: output path where table will be saved, default to ~/downloads/
         """
 
         # save the output path
@@ -110,15 +104,12 @@ class ClassTable(sw.DataTable):
         self.add_btn.on_event("click", self._add_event)
         self.save_btn.on_event("click", self._save_event)
 
-    def populate_table(self, items_file=None):
+    def populate_table(self, items_file: Union[Path, str] = "") -> Self:
         """
         Populate table. It will fill the table with the item contained in the items_file parameter. If no file is provided the table is reset.
 
         Args:
             items (Path object|optional): file containing classes and description
-
-        Return:
-            self
         """
 
         # If there is not any file passed as an argument, populate and empy table
@@ -151,7 +142,7 @@ class ClassTable(sw.DataTable):
 
         return self
 
-    def _save_event(self, widget, event, data):
+    def _save_event(self, *args) -> None:
         """open the save dialog to save the current table in a specific formatted table"""
 
         if not self.items:
@@ -161,7 +152,7 @@ class ClassTable(sw.DataTable):
 
         return
 
-    def _edit_event(self, widget, event, data):
+    def _edit_event(self, *args) -> None:
         """Open the edit dialog and fill it with current line information"""
 
         if not self.v_model:
@@ -171,14 +162,14 @@ class ClassTable(sw.DataTable):
 
         return
 
-    def _add_event(self, widget, event, data):
+    def _add_event(self, *args) -> None:
         """Open the edit dialog to create a new line to the table"""
 
         self.edit_dialog.update()
 
         return
 
-    def _remove_event(self, widget, event, data):
+    def _remove_event(self, *args) -> None:
         """Remove current selection (self.v_model) element from table"""
 
         if not self.v_model:
@@ -196,21 +187,12 @@ class EditDialog(v.Dialog):
 
     TITLES = ms.rec.table.edit_dialog.titles
 
-    def __init__(self, table, **kwargs):
+    def __init__(self, table: ClassTable, **kwargs) -> None:
         """
         Dialog to modify/create new elements from the ClassTable data_table
 
         Args:
-            table (ClassTable, v.DataTable): Table linked with dialog
-
-        Attributes:
-            TITLES (list): the list of potential header (translated at the start of the application)
-            table (classTable): the classTable associated with the dialog
-            title (v.CardTitle): the title of the card ('modify' or 'new')
-            save (sw.Btn): the btn to validate the new line and save it in the datatable
-            modify (sw.Btn): the btn to validate the edited line and save it in the datatable
-            cancel (sw.Btn): the btn to close the dialog without saving anything
-            widgets (list): the list of widget to control the new element state (id, code, description, color) in this order.
+            table: Table linked with dialog
         """
 
         # custom attributes
@@ -274,15 +256,12 @@ class EditDialog(v.Dialog):
         self.modify.on_event("click", self._modify)
         self.cancel.on_event("click", self._cancel)
 
-    def update(self, data=[None, None, None, None]):
+    def update(self, data: list = [None, None, None, None]) -> Self:
         """
         upadte the dialog with the provided information and activate it
 
         Args:
-            data (list): the text value of the selected line (id, code, description, color). default to 4 None (new line)
-
-        Return:
-            self
+            data: the text value of the selected line (id, code, description, color). default to 4 None (new line)
         """
 
         # change the title accodring to the presence of data
@@ -341,7 +320,7 @@ class EditDialog(v.Dialog):
 
         return self
 
-    def _modify(self, widget, event, data):
+    def _modify(self, *args) -> None:
         """Modify elements in the data_table and close the dialog"""
 
         # modify a local copy of the items
@@ -375,7 +354,7 @@ class EditDialog(v.Dialog):
 
         return
 
-    def _save(self, widget, event, data):
+    def _save(self, *args) -> None:
         """Add elements to the table and close the dialog"""
 
         # modify a local copy of the items
@@ -397,7 +376,7 @@ class EditDialog(v.Dialog):
 
         return
 
-    def _cancel(self, widget, event, data):
+    def _cancel(self, *args) -> None:
         """Close dialog and do nothing"""
 
         self.v_model = False
@@ -407,24 +386,16 @@ class EditDialog(v.Dialog):
 
 class SaveDialog(v.Dialog):
 
-    reload = Int().tag(sync=True)
+    reload = Int(0).tag(sync=True)
+    "a traitlet to inform the rest of the app that saving is complete"
 
-    def __init__(self, table, out_path, **kwargs):
+    def __init__(self, table: ClassTable, out_path: Union[str, Path], **kwargs) -> None:
         """
         Dialog to save as .csv file the content of a ClassTable data table
 
         Args:
-            table (ClassTable): Table linked with dialog
-            out_path (str): Folder path to store table content
-
-        Attributes:
-            reload (Int): a traitlet to inform the rest of the app that saving is complete
-            table (ClassTable): Table linked with dialog
-            out_path (str): Folder path to store table content
-            w_file_name (v.TextField): the filename to use in out_path
-            save (sw.Btn): save btn to launch the saving of the table data in the out_path using the filename provided in the widget
-            cancel (sw.Btn): btn to close the dialog and do nothing
-            alert (sw.Alert): an alert to display evolution of the saving process (errors)
+            table: Table linked with dialog
+            out_path: Folder path to store table content
         """
 
         # gather the table and saving params
@@ -481,7 +452,7 @@ class SaveDialog(v.Dialog):
         self.w_file_name.on_event("blur", self._normalize_name)
         self.w_file_name.observe(self._store_info, "v_model")
 
-    def _store_info(self, change):
+    def _store_info(self, change: dict) -> None:
         """Display where will be the file written"""
 
         new_val = change["new"]
@@ -494,12 +465,11 @@ class SaveDialog(v.Dialog):
 
         self.alert.add_msg(msg)
 
-    def show(self):
+        return
+
+    def show(self) -> Self:
         """
         display the dialog and write down the text in the alert
-
-        Return:
-            self
         """
 
         self.v_model = True
@@ -510,7 +480,7 @@ class SaveDialog(v.Dialog):
 
         return self
 
-    def _normalize_name(self, widget, event, data):
+    def _normalize_name(self, widget: v.VuetifyWidget, *args) -> None:
         """Replace the name with it's normalized version"""
 
         # normalized the name
@@ -518,7 +488,7 @@ class SaveDialog(v.Dialog):
 
         return
 
-    def _save(self, widget, event, data):
+    def _save(self, *args) -> None:
         """Write current table on a text file"""
 
         # set the file name
@@ -537,7 +507,7 @@ class SaveDialog(v.Dialog):
 
         return
 
-    def _cancel(self, widget, event, data):
+    def _cancel(self, *args) -> None:
         """hide the widget and do nothing"""
 
         self.v_model = False
@@ -547,36 +517,39 @@ class SaveDialog(v.Dialog):
 
 class TableView(sw.Card):
 
-    title = None
+    title: Optional[v.CardTitle] = None
     "v.CardTitle: the title of the card"
 
-    class_path = None
+    class_path: str = ""
     "str: Folder path containing already existing classes"
 
-    out_path = None
+    out_path: str = ""
     "str: the folder to save the created classifications"
 
-    w_class_file = None
+    w_class_file: Optional[sw.FileInput] = None
     "sw.FileInput: the file input of the existing classification system"
 
-    w_class_table = None
+    w_class_table: Optional[ClassTable] = None
     "ClassTable: the classtable (CRUD) to manage the editing of the classification"
 
-    btn = None
+    btn: Optional[sw.Btn] = None
     "sw.Btn: the btn to start loading file data into the table"
 
-    alert = None
+    alert: Optional[sw.Alert] = None
     "sw.Alert: the alert to display loading information (error and success)"
 
     def __init__(
-        self, class_path=Path.home(), out_path=Path.home() / "downloads", **kwargs
+        self,
+        class_path: Union[str, Path] = Path.home(),
+        out_path: Union[str, Path] = Path.home() / "downloads",
+        **kwargs,
     ):
         """
         Stand-alone Card object allowing the user to build custom class table. The user can start from an existing table or start from scratch. It gives the oportunity to change: the value, the class name and the color. It can be used as a tile in a sepal_ui app. The id\\_ of the tile is set to "classification_tile"
 
         Args:
-            class_path (str|optional): Folder path containing already existing classes. Default to ~/
-            out_path (str|optional): the folder to save the created classifications. default to ~/downloads
+            class_path: Folder path containing already existing classes. Default to ~/
+            out_path: the folder to save the created classifications. default to ~/downloads
         """
 
         # create metadata to make it compatible with the framwork app system
@@ -642,12 +615,9 @@ class TableView(sw.Card):
         self.btn.on_event("click", self.get_class_table)
 
     @sd.loading_button(debug=True)
-    def get_class_table(self, widget, event, data):
+    def get_class_table(self, *args) -> Self:
         """
         Display class table widget in view
-
-        Return:
-            self
         """
 
         # load the existing file into the table
@@ -655,14 +625,11 @@ class TableView(sw.Card):
 
         return self
 
-    def nest_tile(self):
+    def nest_tile(self) -> Self:
         """
         Prepare the view to be used as a nested component in a tile.
         the elevation will be set to 0 and the title remove from children.
         The mount_id will also be changed to nested
-
-        Return:
-            self
         """
 
         # remove id
diff --git a/sepal_ui/scripts/gee.py b/sepal_ui/scripts/gee.py
index f9258000..a3aa17ad 100644
--- a/sepal_ui/scripts/gee.py
+++ b/sepal_ui/scripts/gee.py
@@ -6,10 +6,10 @@ import ee
 import ipyvuetify as v
 
 from sepal_ui.message import ms
-from sepal_ui.scripts import utils as su
+from sepal_ui.scripts import decorator as sd
 
 
-@su.need_ee
+@sd.need_ee
 def wait_for_completion(task_descripsion: str, widget_alert: v.Alert = None) -> str:
     """
     Wait until the selected process is finished. Display some output information
@@ -45,7 +45,7 @@ def wait_for_completion(task_descripsion: str, widget_alert: v.Alert = None) ->
     return state
 
 
-@su.need_ee
+@sd.need_ee
 def is_task(task_descripsion: str) -> ee.batch.Task:
     """
     Search for the described task in the user Task list return None if nothing is found
@@ -66,7 +66,7 @@ def is_task(task_descripsion: str) -> ee.batch.Task:
     return current_task
 
 
-@su.need_ee
+@sd.need_ee
 def is_running(task_descripsion: str) -> ee.batch.Task:
     """
     Search for the described task in the user Task list return None if nothing is currently running
@@ -86,7 +86,7 @@ def is_running(task_descripsion: str) -> ee.batch.Task:
     return current_task
 
 
-@su.need_ee
+@sd.need_ee
 def get_assets(folder: Union[str, Path] = "", asset_list: List[str] = []) -> List[str]:
     """
     Get all the assets from the parameter folder. every nested asset will be displayed.
@@ -113,7 +113,7 @@ def get_assets(folder: Union[str, Path] = "", asset_list: List[str] = []) -> Lis
     return asset_list
 
 
-@su.need_ee
+@sd.need_ee
 def is_asset(asset_name: str, folder: Union[str, Path] = "") -> bool:
     """
     Check if the asset already exist in the user asset folder
diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py
index 40c56a97..74c5f2b3 100644
--- a/sepal_ui/sepalwidgets/inputs.py
+++ b/sepal_ui/sepalwidgets/inputs.py
@@ -17,6 +17,7 @@ from typing_extensions import Self
 from sepal_ui import color
 from sepal_ui.frontend import styles as ss
 from sepal_ui.message import ms
+from sepal_ui.scripts import decorator as sd
 from sepal_ui.scripts import gee
 from sepal_ui.scripts import utils as su
 from sepal_ui.sepalwidgets.btn import Btn
@@ -364,7 +365,7 @@ class FileInput(v.Flex, SepalWidget):
 
         return self
 
-    @su.switch("indeterminate", on_widgets=["loading"])
+    @sd.switch("indeterminate", on_widgets=["loading"])
     def _change_folder(self) -> None:
         """
         Change the target folder
@@ -563,7 +564,7 @@ class LoadTableField(v.Col, SepalWidget):
 
         return
 
-    @su.switch("loading", on_widgets=["IdSelect", "LngSelect", "LatSelect"])
+    @sd.switch("loading", on_widgets=["IdSelect", "LngSelect", "LatSelect"])
     def _on_file_input_change(self, change: dict) -> Self:
         """
         Update the select content when the fileinput v_model is changing
@@ -672,7 +673,7 @@ class AssetSelect(v.Combobox, SepalWidget):
     types: t.List = t.List().tag(sync=True)
     "The list of types accepted by the asset selector. names need to be valide TYPES and changing this value will trigger the reload of the asset items."
 
-    @su.need_ee
+    @sd.need_ee
     def __init__(
         self,
         folder: Union[str, Path] = "",
@@ -725,7 +726,7 @@ class AssetSelect(v.Combobox, SepalWidget):
         self.on_event("click:prepend", self._get_items)
         self.observe(self._get_items, "default_asset")
 
-    @su.switch("loading")
+    @sd.switch("loading")
     def _validate(self, change: dict) -> None:
         """
         Validate the selected asset. Throw an error message if is not accesible or not in the type list.
@@ -754,7 +755,7 @@ class AssetSelect(v.Combobox, SepalWidget):
 
         return
 
-    @su.switch("loading", "disabled")
+    @sd.switch("loading", "disabled")
     def _get_items(self, *args) -> Self:
 
         # init the item list
@@ -819,11 +820,11 @@ class PasswordField(v.TextField, SepalWidget):
         """
 
         # default behavior
-        kwargs["label"] = kwargs.pop("label", "Password")
+        kwargs["label"] = kwargs.pop("label", ms.password_field.label)
         kwargs["class_"] = kwargs.pop("class_", "mr-2")
         kwargs["v_model"] = kwargs.pop("v_model", "")
         kwargs["type"] = "password"
-        kwargs["append_icon"] = kwargs.pop("append_icon", "mdi-eye-off")
+        kwargs["append_icon"] = kwargs.pop("append_icon", "fa-solid fa-eye-slash")
 
         # init the widget with the remaining kwargs
         super().__init__(**kwargs)
@@ -838,10 +839,10 @@ class PasswordField(v.TextField, SepalWidget):
 
         if self.type == "text":
             self.type = "password"
-            self.append_icon = "mdi-eye-off"
+            self.append_icon = "fa-solid fa-eye-slash"
         else:
             self.type = "text"
-            self.append_icon = "mdi-eye"
+            self.append_icon = "fa-solid fa-eye"
 
         return
 
@@ -875,8 +876,10 @@ class NumberField(v.TextField, SepalWidget):
 
         # set default params
         kwargs["type"] = "number"
-        kwargs["append_outer_icon"] = kwargs.pop("append_outer_icon", "mdi-plus")
-        kwargs["prepend_icon"] = kwargs.pop("prepend_icon", "mdi-minus")
+        kwargs["append_outer_icon"] = kwargs.pop(
+            "append_outer_icon", "fa-solid fa-plus"
+        )
+        kwargs["prepend_icon"] = kwargs.pop("prepend_icon", "fa-solid fa-minus")
         kwargs["v_model"] = kwargs.pop("v_model", 0)
         kwargs["readonly"] = kwargs.pop("readonly", True)
 
@@ -998,7 +1001,7 @@ class VectorField(v.Col, SepalWidget):
 
         return self
 
-    @su.switch("loading", on_widgets=["w_column", "w_value"])
+    @sd.switch("loading", on_widgets=["w_column", "w_value"])
     def _update_file(self, change: dict) -> Self:
         """update the file name, the v_model and reset the other widgets"""
 
@@ -1034,7 +1037,7 @@ class VectorField(v.Col, SepalWidget):
 
         return self
 
-    @su.switch("loading", on_widgets=["w_value"])
+    @sd.switch("loading", on_widgets=["w_value"])
     def _update_column(self, change: dict) -> Self:
         """Update the column name and empty the value list"""
 
diff --git a/sepal_ui/sepalwidgets/widget.py b/sepal_ui/sepalwidgets/widget.py
index 0c1d9551..1f332be8 100644
--- a/sepal_ui/sepalwidgets/widget.py
+++ b/sepal_ui/sepalwidgets/widget.py
@@ -1,3 +1,4 @@
+from pathlib import Path
 from typing import Optional, Union
 
 import ipyvuetify as v
@@ -81,22 +82,11 @@ class CopyToClip(v.VuetifyTemplate):
         self.components = {"mytf": self.tf}
 
         # template with js behaviour
-        self.template = """
-        <mytf/>
-        <script>
-            {methods: {
-                jupyter_clip(_txt) {
-                    var tempInput = document.createElement("input");
-                    tempInput.value = _txt;
-                    document.body.appendChild(tempInput);
-                    tempInput.focus();
-                    tempInput.select();
-                    document.execCommand("copy");
-                    document.body.removeChild(tempInput);
-                }
-            }}
-        </script>
-        """
+        js_dir = Path(__file__).parents[1] / "frontend/js"
+        clip = (js_dir / "jupyter_clip.js").read_text()
+        self.template = (
+            "<mytf/>" "<script>{methods: {jupyter_clip(_txt) {%s}}}</script>" % clip
+        )
 
         super().__init__()
 


Test patch:
diff --git a/tests/check_warnings.py b/tests/check_warnings.py
new file mode 100644
index 00000000..af336d96
--- /dev/null
+++ b/tests/check_warnings.py
@@ -0,0 +1,56 @@
+import sys
+from pathlib import Path
+
+
+def check_warnings(file):
+    """
+    Check the list of warnings produced by the GitHub CI tests
+    raise errors if there are unexpected ones and/or if some are missing
+
+    Args:
+        file (pathlib.Path): the path to the generated warning.txt file from
+            the CI build
+
+    Return:
+        0 if the warnings are all there
+        1 if some warning are not registered or unexpected
+    """
+
+    # print some log
+    print("\n=== Sphinx Warnings test ===\n")
+
+    # find the file where all the known warnings are stored
+    warning_file = Path(__file__).parent / "data" / "warning_list.txt"
+
+    test_warnings = file.read_text().strip().split("\n")
+    ref_warnings = warning_file.read_text().strip().split("\n")
+
+    print(
+        f'Checking build warnings in file: "{file}" and comparing to expected '
+        f'warnings defined in "{warning_file}"\n\n'
+    )
+
+    # find all the missing warnings
+    missing_warnings = []
+    for wa in ref_warnings:
+        index = [i for i, twa in enumerate(test_warnings) if wa in twa]
+        if len(index) == 0:
+            missing_warnings += [wa]
+            print(f"Warning was not raised: {wa}\n")
+        else:
+            test_warnings.pop(index[0])
+
+    # the remaining one are unexpected
+    for twa in test_warnings:
+        print(f"Unexpected warning: {twa}\n")
+
+    return len(missing_warnings) != 0 or len(test_warnings) != 0
+
+
+if __name__ == "__main__":
+
+    # cast the file to path and resolve to an absolute one
+    file = Path.cwd() / "warnings.txt"
+
+    # execute the test
+    sys.exit(check_warnings(file))
diff --git a/tests/data/warning_list.txt b/tests/data/warning_list.txt
new file mode 100644
index 00000000..c4c475f8
--- /dev/null
+++ b/tests/data/warning_list.txt
@@ -0,0 +1,3 @@
+ERROR: Unknown target name: "type".
+ERROR: Document or section may not begin with a transition.
+WARNING: document isn't included in any toctree
\ No newline at end of file
diff --git a/tests/test_AoiModel.py b/tests/test_AoiModel.py
index f240e543..cba391be 100644
--- a/tests/test_AoiModel.py
+++ b/tests/test_AoiModel.py
@@ -9,36 +9,36 @@ from sepal_ui import aoi
 
 
 class TestAoiModel:
-    def test_init_no_ee(self, alert, fake_vector):
+    def test_init_no_ee(self, fake_vector):
 
         # default init
-        aoi_model = aoi.AoiModel(alert, gee=False)
+        aoi_model = aoi.AoiModel(gee=False)
         assert isinstance(aoi_model, aoi.AoiModel)
         assert aoi_model.gee is False
 
         # with a default vector
-        aoi_model = aoi.AoiModel(alert, vector=fake_vector, gee=False)
+        aoi_model = aoi.AoiModel(vector=fake_vector, gee=False)
         assert aoi_model.name == "gadm36_VAT_0"
 
         # test with a non ee admin
         admin = "VAT"  # GADM Vatican city
-        aoi_model = aoi.AoiModel(alert, gee=False, admin=admin)
+        aoi_model = aoi.AoiModel(gee=False, admin=admin)
 
         assert aoi_model.name == "VAT"
 
         return
 
     @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set")
-    def test_init_ee(alert, gee_dir):
+    def test_init_ee(self, gee_dir):
 
         # default init
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir)
+        aoi_model = aoi.AoiModel(folder=gee_dir)
         assert isinstance(aoi_model, aoi.AoiModel)
         assert aoi_model.gee is True
 
         # with default assetId
         asset_id = str(gee_dir / "feature_collection")
-        aoi_model = aoi.AoiModel(alert, asset=asset_id, folder=gee_dir)
+        aoi_model = aoi.AoiModel(asset=asset_id, folder=gee_dir)
 
         assert aoi_model.asset_name == asset_id
         assert aoi_model.default_asset == asset_id
@@ -48,23 +48,23 @@ class TestAoiModel:
 
         # check that wrongly defined asset_name raise errors
         with pytest.raises(Exception):
-            aoi_model = aoi.AoiModel(alert, folder=gee_dir)
+            aoi_model = aoi.AoiModel(folder=gee_dir)
             aoi_model._from_asset({"pathname": None})
 
         with pytest.raises(Exception):
-            aoi_model = aoi.AoiModel(alert, folder=gee_dir)
+            aoi_model = aoi.AoiModel(folder=gee_dir)
             asset = {"pathname": asset_id, "column": "data", "value": None}
             aoi_model._from_asset(asset)
 
         # it should be the same with a different name
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir)
+        aoi_model = aoi.AoiModel(folder=gee_dir)
         asset = {"pathname": asset_id, "column": "data", "value": 0}
         aoi_model._from_asset(asset)
         assert aoi_model.name == "feature_collection_data_0"
 
         # with a default admin
         admin = "110"  # GAUL Vatican city
-        aoi_model = aoi.AoiModel(alert, admin=admin, folder=gee_dir)
+        aoi_model = aoi.AoiModel(admin=admin, folder=gee_dir)
         assert aoi_model.name == "VAT"
 
         return
@@ -121,7 +121,7 @@ class TestAoiModel:
 
         return
 
-    def test_clear_attributes(self, alert, aoi_model_outputs, aoi_model_traits):
+    def test_clear_attributes(self, aoi_model_outputs, aoi_model_traits):
 
         aoi_model = aoi.AoiModel(gee=False)
 
@@ -151,7 +151,7 @@ class TestAoiModel:
         assert all([is_none(out) for out in aoi_model_outputs])
 
         # check that default are saved
-        aoi_model = aoi.AoiModel(alert, admin="VAT", gee=False)  # GADM for Vatican
+        aoi_model = aoi.AoiModel(admin="VAT", gee=False)  # GADM for Vatican
 
         # insert dummy parameter
         [set_trait(trait) for trait in aoi_model_traits]
@@ -192,9 +192,9 @@ class TestAoiModel:
 
         return
 
-    def test_set_object(self, alert):
+    def test_set_object(self):
 
-        aoi_model = aoi.AoiModel(alert, gee=False)
+        aoi_model = aoi.AoiModel(gee=False)
 
         # test that no method returns an error
         with pytest.raises(Exception):
@@ -202,9 +202,9 @@ class TestAoiModel:
 
         return
 
-    def test_from_admin(self, alert, gee_dir):
+    def test_from_admin(self, gee_dir):
 
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir)
+        aoi_model = aoi.AoiModel(folder=gee_dir)
 
         # with fake number
         with pytest.raises(Exception):
@@ -217,9 +217,9 @@ class TestAoiModel:
         return
 
     @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set")
-    def test_from_point(self, alert, fake_points, gee_dir):
+    def test_from_point(self, fake_points, gee_dir):
 
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir, gee=False)
+        aoi_model = aoi.AoiModel(folder=gee_dir, gee=False)
 
         # uncomplete json
         points = {
@@ -244,9 +244,9 @@ class TestAoiModel:
         return
 
     @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set")
-    def test_from_vector(self, alert, gee_dir, fake_vector):
+    def test_from_vector(self, gee_dir, fake_vector):
 
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir, gee=False)
+        aoi_model = aoi.AoiModel(folder=gee_dir, gee=False)
 
         # with no pathname
         with pytest.raises(Exception):
@@ -270,9 +270,9 @@ class TestAoiModel:
         return
 
     @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set")
-    def test_from_geo_json(self, alert, gee_dir, square):
+    def test_from_geo_json(self, gee_dir, square):
 
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir, gee=False)
+        aoi_model = aoi.AoiModel(folder=gee_dir, gee=False)
 
         # no points
         with pytest.raises(Exception):
@@ -286,11 +286,11 @@ class TestAoiModel:
         return
 
     @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set")
-    def test_from_asset(self, alert, gee_dir):
+    def test_from_asset(self, gee_dir):
 
         # init parameters
         asset_id = str(gee_dir / "feature_collection")
-        aoi_model = aoi.AoiModel(alert, folder=gee_dir)
+        aoi_model = aoi.AoiModel(folder=gee_dir)
 
         # no asset name
         with pytest.raises(Exception):
@@ -381,12 +381,12 @@ class TestAoiModel:
         return
 
     @pytest.fixture
-    def test_model(self, alert, gee_dir):
+    def test_model(self, gee_dir):
         """
         Create a test AoiModel based on GEE using Vatican
         """
         admin = "110"  # vatican city (smalest adm0 feature)
-        return aoi.AoiModel(alert, admin=admin, folder=gee_dir)
+        return aoi.AoiModel(admin=admin, folder=gee_dir)
 
     @pytest.fixture(scope="class")
     def aoi_model_traits(self):
diff --git a/tests/test_PasswordField.py b/tests/test_PasswordField.py
index 5d930a63..d4ff8855 100644
--- a/tests/test_PasswordField.py
+++ b/tests/test_PasswordField.py
@@ -16,12 +16,12 @@ class TestPasswordField:
         # change the viz once
         password._toggle_pwd(None, None, None)
         assert password.type == "text"
-        assert password.append_icon == "mdi-eye"
+        assert password.append_icon == "fa-solid fa-eye"
 
         # change it a second time
         password._toggle_pwd(None, None, None)
         assert password.type == "password"
-        assert password.append_icon == "mdi-eye-off"
+        assert password.append_icon == "fa-solid fa-eye-slash"
 
         return
 
diff --git a/tests/test_ReclassifyView.py b/tests/test_ReclassifyView.py
index ad7124bc..84ad221e 100644
--- a/tests/test_ReclassifyView.py
+++ b/tests/test_ReclassifyView.py
@@ -11,7 +11,7 @@ from sepal_ui.reclassify import ReclassifyModel, ReclassifyView
 
 class TestReclassifyView:
     @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set")
-    def test_init_exception(alert, gee_dir):
+    def test_init_exception(self, gee_dir):
         """Test exceptions"""
 
         aoi_model = aoi.AoiModel(gee=False)
@@ -146,10 +146,10 @@ class TestReclassifyView:
         return
 
     @pytest.fixture
-    def view_local(self, tmp_dir, class_file, alert):
+    def view_local(self, tmp_dir, class_file):
         """return a local reclassify view"""
 
-        aoi_model = aoi.AoiModel(alert, gee=False)
+        aoi_model = aoi.AoiModel(gee=False)
 
         return ReclassifyView(
             aoi_model=aoi_model,
@@ -160,10 +160,10 @@ class TestReclassifyView:
         )
 
     @pytest.fixture
-    def view_gee(self, tmp_dir, class_file, gee_dir, alert):
+    def view_gee(self, tmp_dir, class_file, gee_dir):
         """return a gee reclassify view"""
 
-        aoi_model = aoi.AoiModel(alert, gee=True, folder=str(gee_dir))
+        aoi_model = aoi.AoiModel(gee=True, folder=str(gee_dir))
 
         return ReclassifyView(
             aoi_model=aoi_model,