diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f7c8f34b08ba2e23c64b5801f6885b3c1768ac72..bd01e22edd47c77eb35b09eff7b41198f1463721 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,5 @@
 stages:
+  - test-build
   - Static analysis
   - build
   - deploy
@@ -8,6 +9,17 @@ variables:
   SONAR_HOST_URL: "https://sonarqube.neogeo.fr"
   GIT_DEPTH: 0
 
+test build:
+  stage: test-build
+  image: node:14
+  script:
+    - npm install
+    - npm run build
+  except:
+    - master
+    - develop
+    - ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+
 build testing docker image:
   stage: build
   only:
diff --git a/src/assets/js/utils.js b/src/assets/js/utils.js
index 4b01c67ef07d0bf803528976e6708657894fa821..0680fcdfad82586d6325e4cc501bf9873abf4d7c 100644
--- a/src/assets/js/utils.js
+++ b/src/assets/js/utils.js
@@ -24,14 +24,18 @@ export function csvToJson(csv, delimiter) {
   const [, ...lines] = allLines;
 
   for (const line of lines) {
-    const obj = {};
-    const currentLine = line.split(delimiter);
+    if (line) {
+      const obj = {};
+      const currentLine = line.split(delimiter).map(el => {
+        return el.replace('\r', '');
+      });
 
-    for (let i = 0; i < headers.length; i++) {
-      obj[headers[i]] = currentLine[i];
-    }
+      for (let i = 0; i < headers.length; i++) {
+        obj[headers[i]] = currentLine[i];
+      }
 
-    result.push(obj);
+      result.push(obj);
+    }
   }
   return JSON.parse(JSON.stringify(result));
 }
diff --git a/src/assets/styles/base.css b/src/assets/styles/base.css
index 5383b368b37b82b882868fa28a18e8d0180401ba..00f8d4f9a991c0cfd25f9bdda21fd88d49e6c5fb 100644
--- a/src/assets/styles/base.css
+++ b/src/assets/styles/base.css
@@ -81,6 +81,9 @@ body {
 .tiny-margin {
   margin:  0.1rem 0 0.1rem 0.1rem !important;
 }
+.tiny-margin-left {
+  margin-left:  0.1rem !important;
+}
 .ellipsis {
   text-overflow: ellipsis;
   overflow: hidden;
@@ -163,6 +166,12 @@ body {
   color: #9f3a38;
 }
 
+#form-layers .infoslist{
+  list-style: none;
+  padding-left: 0;
+  color: #38989f;
+}
+
 /* Fix semantic ui overflow when is too long */
 .layer-item .form div.text {
   width: 100%
@@ -225,13 +234,26 @@ body {
   padding: 0;
 }
 
+.infoslist {
+  margin-top: 0.1rem;
+  padding: 0;
+}
+
 .errorlist > li {
   list-style: none;
   color: rgb(177, 55, 55);
   border:  thin solid rgb(197, 157, 157);
   border-radius: 3px;
   background-color: rgb(250, 241, 242);
-  padding: 1rem;
+  padding: 0.5rem 1rem;
+}
+
+.infoslist > li {
+  list-style: none;
+  color: #38989f;
+  border-radius: 3px;
+  padding: 0;
+  text-align: right;
 }
 
 /* ---------------------------------- */
diff --git a/src/components/Feature/Detail/FeatureHeader.vue b/src/components/Feature/Detail/FeatureHeader.vue
index d0850cb749761cd9462f53e69a904c8a43046ea4..eb75b473eb99c429536067ffe80928bdea61cbd2 100644
--- a/src/components/Feature/Detail/FeatureHeader.vue
+++ b/src/components/Feature/Detail/FeatureHeader.vue
@@ -45,7 +45,7 @@
             v-if="((permissions && permissions.can_update_feature) || isFeatureCreator) && isOnline"
             id="currentFeature-delete"
             class="ui button button-hover-red"
-            @click="isCanceling = true"
+            @click="$emit('setIsCancelling')"
           >
             <i
               class="inverted grey trash alternate icon"
diff --git a/src/components/Map/EditingToolbar.vue b/src/components/Map/EditingToolbar.vue
index 3465c628b02185d7b575e2712c827a20863c2766..1f4d19aa178dca42f6435eba8659e12464bce79b 100644
--- a/src/components/Map/EditingToolbar.vue
+++ b/src/components/Map/EditingToolbar.vue
@@ -4,7 +4,11 @@
       <div class="leaflet-bar">
         <a
           class="leaflet-draw-draw-polygon active"
-          title="Dessiner un polygone"
+          :title="
+            editionService.geom_type === 'polygon' ? 'Dessiner un polygone' :
+            editionService.geom_type === 'linestring' ? 'Dessiner une ligne' :
+            'Dessiner un point'
+          "
         >
           <img
             v-if="editionService.geom_type === 'linestring'"
diff --git a/src/components/Project/Detail/ProjectHeader.vue b/src/components/Project/Detail/ProjectHeader.vue
index c56cb750c0414bee19ffb6b72641c8d9a73d1862..35d1871095f4b433b15415a0a40d81292cd37f68 100644
--- a/src/components/Project/Detail/ProjectHeader.vue
+++ b/src/components/Project/Detail/ProjectHeader.vue
@@ -255,11 +255,12 @@ export default {
           this.arraysOfflineErrors.push(feature);
         })
       );
-      this.DISPLAY_LOADER('Envoi des signalements en cours.');
+      this.$store.commit('DISPLAY_LOADER', 'Envoi des signalements en cours.');
 
       Promise.all(promises).then(() => {
-        this.$emit('update-local-storage');
-        this.$emit('retrieve-info');
+        this.$emit('updateLocalStorage');
+        this.$emit('retrieveInfo');
+        this.$store.commit('DISCARD_LOADER');
       });
     },
 
@@ -272,24 +273,20 @@ export default {
 
 .project-header {
 
-  .row {
-    margin-top: 3em;
+  .row .right-column {
+    display: flex;
+    flex-direction: column;
 
-    .right-column {
-      display: flex;
-      flex-direction: column;
-
-      .ui.buttons {
-        justify-content: flex-end;
-        a.ui.button {
-          flex-grow: 0; /* avoid stretching buttons */
-        }
+    .ui.buttons {
+      justify-content: flex-end;
+      a.ui.button {
+        flex-grow: 0; /* avoid stretching buttons */
       }
     }
-    .centered {
-      margin: auto;
-      text-align: center;
-    }
+  }
+  .centered {
+    margin: auto;
+    text-align: center;
   }
 }
 
diff --git a/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue b/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue
index 4e624f10378ccc3b988577ab543db618af3d6ee8..7416a8f72d95420e4d3ab9fc506a6daf38f37ad6 100644
--- a/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue
+++ b/src/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters.vue
@@ -85,7 +85,7 @@
             </div>
             <div
               v-if="checkedFeatures.length > 0 && massMode === 'modify'"
-              class="ui dropdown button compact button-hover-green margin-left-25"
+              class="ui dropdown button compact button-hover-green tiny-margin-left"
               data-tooltip="Modifier le statut des Signalements"
               data-position="bottom right"
               @click="toggleModifyStatus"
@@ -107,7 +107,7 @@
                     v-for="status in availableStatus"
                     :key="status.value"
                     class="item"
-                    @click="modifyStatus(status.value)"
+                    @click="$emit('modify-status', status.value)"
                   >
                     {{ status.name }}
                   </span>
@@ -116,10 +116,10 @@
             </div>
             <div
               v-if="checkedFeatures.length > 0 && massMode === 'delete'"
-              class="ui button compact button-hover-red margin-left-25"
+              class="ui button compact button-hover-red tiny-margin-left"
               data-tooltip="Supprimer tous les signalements sélectionnés"
               data-position="bottom right"
-              @click="toggleDeleteModal"
+              @click="$emit('toggle-delete-modal')"
             >
               <i
                 class="grey trash fitted icon"
@@ -320,6 +320,14 @@ export default {
     },
   },
 
+  mounted() {   
+    window.addEventListener('mousedown', this.clickOutsideDropdown);
+  },
+
+  destroyed() {
+    window.removeEventListener('mousedown', this.clickOutsideDropdown);
+  },
+
   methods: {
     resetPaginationNfetchFeatures() {
       this.$emit('reset-pagination');
@@ -330,6 +338,20 @@ export default {
       this.showAddFeature = !this.showAddFeature;
       this.showModifyStatus = false;
     },
+
+    toggleModifyStatus() {
+      this.showModifyStatus = !this.showModifyStatus;
+      this.showAddFeature = false;
+    },
+
+    clickOutsideDropdown(e) {
+      if (!e.target.closest('#button-dropdown')) {
+        this.showModifyStatus = false;
+        setTimeout(() => { //* timout necessary to give time to click on link to add feature
+          this.showAddFeature = false;
+        }, 500);
+      }
+    },
   }
 
 };
diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js
index d9165a280ec265551fe559549fbfdab19a934813..e2cd38783734eb6519d7705e7c8f1379eb914803 100644
--- a/src/registerServiceWorker.js
+++ b/src/registerServiceWorker.js
@@ -26,15 +26,19 @@ if (process.env.NODE_ENV === 'production') {
       console.log('New content is downloading.');
     },
     updated (registration) {
-      alert('Une nouvelle version de l\'application est disponible, l\'application va se recharger');
-      console.log('New content is available; please refresh.');
-      //
-      if (!registration || !registration.waiting) {
-        return;
+      if (!navigator.webdriver) {
+        alert('Une nouvelle version de l\'application est disponible, l\'application va se recharger');
+        console.log('New content is available; please refresh.');
+        //
+        if (!registration || !registration.waiting) {
+          return;
+        }
+        // Send message to SW to skip the waiting and activate the new SW
+        registration.waiting.postMessage({ type: 'SKIP_WAITING' });
+        //window.location.reload(true);
+      } else {
+        console.log('Execution dans un navigateur controlé par un agent automatisé, la mise à jour n\'est pas appliqué pendant le test.');
       }
-      // Send message to SW to skip the waiting and activate the new SW
-      registration.waiting.postMessage({ type: 'SKIP_WAITING' });
-      //window.location.reload(true);
     },
     offline () {
       console.log('No internet connection found. App is running in offline mode.');
diff --git a/src/services/map-service.js b/src/services/map-service.js
index 7a9afd5e6c7d5643bb181dec4c62aa067b78472b..e88fceab2a2c826dca7ab2a0baa32258168f09bc 100644
--- a/src/services/map-service.js
+++ b/src/services/map-service.js
@@ -116,7 +116,7 @@ const mapService = {
         overlay.setPosition(undefined);
         closer.blur();
         return false;
-      };
+      }; 
     }
     this.map.addOverlay(this.overlay);
 
@@ -244,8 +244,11 @@ const mapService = {
   addLayers: function (layers, serviceMap, optionsMap, schemaType) {
     this.layers = layers;
     if (layers) { //* if admin has defined basemaps for this project
+      let count = 0;
       layers.forEach((layer) => {
+
         if (layer) {
+          count +=1;
           const options = layer.options;
           if (options) {
             options.noWrap = true;
@@ -268,6 +271,7 @@ const mapService = {
               dictLayersToLeaflet[layer.id] = layerTms;
             }
           }
+          dictLayersToLeaflet[layer.id].setZIndex(count);
         }
       });
     } else { //* else when no basemaps defined
@@ -358,6 +362,7 @@ const mapService = {
     });
     this.mvtLayer.featureTypes = featureTypes;
     this.mvtLayer.project_slug = projectSlug;
+    this.mvtLayer.setZIndex(30);
     this.map.addLayer(this.mvtLayer);
     window.layerMVT = this.mvtLayer;
   },
@@ -462,7 +467,7 @@ const mapService = {
       source: drawSource,
       style: styleFunction,
     });
-
+    olLayer.setZIndex(29);
     this.map.addLayer(olLayer);
     return drawSource;
   },
diff --git a/src/store/modules/map.store.js b/src/store/modules/map.store.js
index 1a87098abcbd86423c00101f56f2b19fcc7061c1..f4a65434bb4e026166436892f73d24b23752f5be 100644
--- a/src/store/modules/map.store.js
+++ b/src/store/modules/map.store.js
@@ -121,25 +121,29 @@ const map = {
     },
 
     async SAVE_BASEMAPS({ state, rootState, dispatch }, newBasemapIds) {
-      const DJANGO_API_BASE = this.state.configuration.VUE_APP_DJANGO_API_BASE;
-      function postOrPut(basemap) {
-        basemap['project'] = rootState.projects.project.slug;
-        if (newBasemapIds.includes(basemap.id)) {
-          return axios
-            .post(`${DJANGO_API_BASE}base-maps/`, basemap)
-            .then((response) => response)
-            .catch((error) => {
-              console.error(error);
-              return error;
-            });
-        } else {
-          return axios
-            .put(`${DJANGO_API_BASE}base-maps/${basemap.id}/`, basemap)
-            .then((response) => response)
-            .catch((error) => {
-              console.error(error);
-              return error;
-            });
+      //* send new basemaps synchronously to create their ids in the order they were created in the form
+      let promisesResult = [];
+      function postOrPut(basemapsToSend) {
+        if (basemapsToSend.length > 0) {
+          let basemap = basemapsToSend.shift(); //* remove and return first item in array
+          basemap['project'] = rootState.projects.project.slug;
+          let url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}base-maps/`;
+          if (!newBasemapIds.includes(basemap.id)) url += `${basemap.id}/`;
+          promisesResult.push(
+            axios({
+              url,
+              method: newBasemapIds.includes(basemap.id) ? 'POST' : 'PUT',
+              data: basemap,
+            })
+              .then((response) => {
+                postOrPut(basemapsToSend);
+                return response;
+              })
+              .catch((error) => {
+                postOrPut(basemapsToSend);
+                return error;
+              })
+          );
         }
       }
 
@@ -149,14 +153,11 @@ const map = {
           .then((response) => response);
       }
 
-      const promisesResult = await Promise.all(
-        [
-          ...state.basemaps.map((basemap) => postOrPut(basemap)),
-          ...state.basemapsToDelete.map((basemapId) => deleteBMap(basemapId))
-        ]
-      );
+      postOrPut([...state.basemaps]);
+      //* delete basemaps
+      const deletedResult = await Promise.all(state.basemapsToDelete.map((basemapId) => deleteBMap(basemapId)));
       state.basemapsToDelete = [];
-      return promisesResult;
+      return [...promisesResult, ...deletedResult];
     },
 
     DELETE_BASEMAP({ commit }, basemapId) {
diff --git a/src/views/Feature/FeatureDetail.vue b/src/views/Feature/FeatureDetail.vue
index f6e76a9e232606c23e033a21574bb9e7f92875fc..946b4629a960d30fa743f8ddb91a28f4139bdde2 100644
--- a/src/views/Feature/FeatureDetail.vue
+++ b/src/views/Feature/FeatureDetail.vue
@@ -9,7 +9,9 @@
     >
       <div class="row">
         <div class="sixteen wide column">
-          <FeatureHeader />
+          <FeatureHeader
+            @setIsCancelling="isCanceling = true"
+          />
         </div>
       </div>
       <div class="row">
@@ -52,7 +54,7 @@
       </div>
       <div
         v-if="isCanceling"
-        class="ui dimmer modals page transition visible active"
+        class="ui dimmer modals transition visible active"
         style="display: flex !important"
       >
         <div
@@ -234,9 +236,9 @@ export default {
     deleteFeature() {
       this.$store
         .dispatch('feature/DELETE_FEATURE', { feature_id: this.currentFeature.feature_id })
-        .then((response) => {
+        .then(async (response) => {
           if (response.status === 204) {
-            this.GET_PROJECT_FEATURES({
+            await this.GET_PROJECT_FEATURES({
               project_slug: this.$route.params.slug
             });
             this.goBackToProject();
diff --git a/src/views/Feature/FeatureEdit.vue b/src/views/Feature/FeatureEdit.vue
index f024fc524b7464e29341c37fc4cfe41c064e3855..567fd72ba19ce0b15e7335b5f033acb9142d9fbc 100644
--- a/src/views/Feature/FeatureEdit.vue
+++ b/src/views/Feature/FeatureEdit.vue
@@ -32,6 +32,17 @@
             :name="form.title.html_name"
             @blur="updateStore"
           >
+          <ul
+            id="infoslist-title"
+            class="infoslist"
+          >
+            <li
+              v-for="info in form.title.infos"
+              :key="info"
+            >
+              {{ info }}
+            </li>
+          </ul>
           <ul
             id="errorlist-title"
             class="errorlist"
@@ -379,9 +390,10 @@ export default {
       form: {
         title: {
           errors: [],
+          infos: [],
           id_for_label: 'name',
           field: {
-            max_length: 30,
+            max_length: 128,
           },
           html_name: 'name',
           label: 'Nom',
@@ -491,6 +503,16 @@ export default {
     },
   },
 
+  watch: {
+    'form.title.value': function(newValue) {
+      if (newValue.length === 128) {
+        this.form.title.infos.push('Le nombre de caractères et limité à 128.');
+      } else {
+        this.form.title.infos = [];
+      }
+    }
+  },
+
   created() {
     this.$store.commit(
       'feature-type/SET_CURRENT_FEATURE_TYPE_SLUG',
diff --git a/src/views/FeatureType/FeatureTypeDetail.vue b/src/views/FeatureType/FeatureTypeDetail.vue
index 6ff4e62769af70ea10197250a31e59d0db177112..58303368fb891af77f861fecb6985e2ed83d9ac8 100644
--- a/src/views/FeatureType/FeatureTypeDetail.vue
+++ b/src/views/FeatureType/FeatureTypeDetail.vue
@@ -611,12 +611,11 @@ export default {
       const headersLine =
         csv
           .split('\n')[0]
+          .replace(/(\r\n|\n|\r)/gm, '')
           .split(delimiter)
           .filter(el => {
             return el === 'lat' || el === 'lon';
           });
-      // Look for 2 decimal fields in first line of csv
-      // corresponding to lon and lat
       if (headersLine.length !== 2) {
         this.importError = 'Le fichier ne semble pas contenir de champs de coordonnées.';
         return false;
@@ -664,7 +663,8 @@ export default {
                 //* if field type is list, it's not possible to guess from value type
                 if (field_type === 'list') {
                   //*then check if the value is an available option
-                  if (!options.includes(fieldInFeature)) {
+                  if (fieldInFeature && !options.includes(fieldInFeature)) {
+                    this.importError = `Le champ ${name} contient une valeur invalide.`;
                     return false;
                   }
                 } else if (customType !== field_type) {
@@ -694,7 +694,7 @@ export default {
       reader.addEventListener('load', (e) => {
         // bypass json check for files larger then 10 Mo
         let jsonValidity;
-        if (parseFloat(fileConvertSizeToMo(files[0])) <= 10) {
+        if (parseFloat(fileConvertSizeToMo(files[0].size)) <= 10) {
           jsonValidity = this.checkJsonValidity(JSON.parse(e.target.result));
         } else {
           jsonValidity = true;
diff --git a/src/views/FeatureType/FeatureTypeEdit.vue b/src/views/FeatureType/FeatureTypeEdit.vue
index 67d6def4ac8f5dab0ad86109b2c411d25e4f0afa..303f3d2f59107f7d8e031dae3e24aa8308d13d28 100644
--- a/src/views/FeatureType/FeatureTypeEdit.vue
+++ b/src/views/FeatureType/FeatureTypeEdit.vue
@@ -244,6 +244,7 @@ export default {
       slug: this.$route.params.slug,
       reservedKeywords: [
         // todo : add keywords for mapstyle (strokewidth...)
+        'id',
         'title',
         'description',
         'status',
diff --git a/src/views/Project/FeaturesListAndMap.vue b/src/views/Project/FeaturesListAndMap.vue
index 6b6af5454d03509df24be6f07478676b40d5138d..ec2d2bd5012c056ab30e1f0f0b4dc52f719368e7 100644
--- a/src/views/Project/FeaturesListAndMap.vue
+++ b/src/views/Project/FeaturesListAndMap.vue
@@ -12,6 +12,8 @@
         @reset-pagination="resetPagination"
         @fetch-features="fetchPagedFeatures"
         @show-map="setShowMap"
+        @modify-status="modifyStatus"
+        @toggle-delete-modal="toggleDeleteModal"
       />
 
       <div
@@ -143,8 +145,6 @@ export default {
       pagination: { ...initialPagination },
       projectSlug: this.$route.params.slug,
       showMap: true,
-      showAddFeature: false,
-      showModifyStatus: false,
       sort: {
         column: '',
         ascending: true,
@@ -223,12 +223,9 @@ export default {
     } else {
       this.initMap();
     }
-   
-    window.addEventListener('mousedown', this.clickOutsideDropdown);
   },
 
   destroyed() {
-    window.removeEventListener('mousedown', this.clickOutsideDropdown);
     //* allow user to change page if ever stuck on loader
     this.$store.commit('DISCARD_LOADER');
   },
@@ -260,24 +257,10 @@ export default {
       }
     },
 
-    toggleModifyStatus() {
-      this.showModifyStatus = !this.showModifyStatus;
-      this.showAddFeature = false;
-    },
-
     toggleDeleteModal() {
       this.isDeleteModalOpen = !this.isDeleteModalOpen;
     },
 
-    clickOutsideDropdown(e) {
-      if (!e.target.closest('#button-dropdown')) {
-        this.showModifyStatus = false;
-        setTimeout(() => { //* timout necessary to give time to click on link to add feature
-          this.showAddFeature = false;
-        }, 500);
-      }
-    },
-
     async modifyStatus(newStatus) {
       if (this.checkedFeatures.length > 0) {
         const feature_id = this.checkedFeatures[0];
diff --git a/src/views/Project/ProjectDetail.vue b/src/views/Project/ProjectDetail.vue
index 452a9067c61469a8eeefc0884de1f0d0fd6eb5ea..58ff359a5d337ebc6dc58fcbfdb815f183517a25 100644
--- a/src/views/Project/ProjectDetail.vue
+++ b/src/views/Project/ProjectDetail.vue
@@ -437,7 +437,7 @@ export default {
         await this.INITIATE_MAP(this.$refs.map);
         this.checkForOfflineFeature();
         let project_id = this.$route.params.slug.split('-')[0];
-        const mvtUrl = `${this.API_BASE_URL}features.mvt/`;
+        const mvtUrl = `${this.API_BASE_URL}features.mvt`;
         mapService.addVectorTileLayer(
           mvtUrl,
           project_id,