diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 380f359efdc1c01cad2bd4aa9f8f62f865b46765..5e66729357c07d0423be54d983eaf2114fdbfce5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -136,7 +136,7 @@ eslint:
     - cd front
     - yarn install
   script:
-    - yarn lint
+    - yarn lint --max-warnings 0
   cache:
     key: "$CI_PROJECT_ID__eslint_npm_cache"
     paths:
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index aba5a346d04b40787297dbc92b30132322f83209..33c54c2c53343410bfbc4da39cdd399e9db16f77 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -133,20 +133,20 @@ export default {
   mixins: [ReportMixin, PlayOptionsMixin],
   props: {
     // we can either have a single or multiple tracks to play when clicked
-    tracks: { type: Array, required: false, default: () => { return [] } },
-    track: { type: Object, required: false, default: () => { return {} } },
-    account: { type: Object, required: false, default: () => { return {} } },
+    tracks: { type: Array, required: false, default: () => { return null } },
+    track: { type: Object, required: false, default: () => { return null } },
+    account: { type: Object, required: false, default: () => { return null } },
     dropdownIconClasses: { type: Array, required: false, default: () => { return ['dropdown'] } },
     playIconClass: { type: String, required: false, default: 'play icon' },
     buttonClasses: { type: Array, required: false, default: () => { return ['button'] } },
-    playlist: { type: Object, required: false, default: () => { return {} } },
+    playlist: { type: Object, required: false, default: () => { return null } },
     discrete: { type: Boolean, default: false },
     dropdownOnly: { type: Boolean, default: false },
     iconOnly: { type: Boolean, default: false },
-    artist: { type: Object, required: false, default: () => { return {} } },
-    album: { type: Object, required: false, default: () => { return {} } },
-    library: { type: Object, required: false, default: () => { return {} } },
-    channel: { type: Object, required: false, default: () => { return {} } },
+    artist: { type: Object, required: false, default: () => { return null } },
+    album: { type: Object, required: false, default: () => { return null } },
+    library: { type: Object, required: false, default: () => { return null } },
+    channel: { type: Object, required: false, default: () => { return null } },
     isPlayable: { type: Boolean, required: false, default: null },
     playing: { type: Boolean, required: false, default: false },
     paused: { type: Boolean, required: false, default: false }
diff --git a/front/src/components/audio/VolumeControl.vue b/front/src/components/audio/VolumeControl.vue
index aa61c89b83b8c1968b4838d6fbd3752a68de5b69..d8f45dccc8dc2333e5f5c536c41968c6d4b56166 100644
--- a/front/src/components/audio/VolumeControl.vue
+++ b/front/src/components/audio/VolumeControl.vue
@@ -50,7 +50,7 @@
   </button>
 </template>
 <script>
-import mapActions from 'vuex'
+import { mapActions } from 'vuex'
 
 export default {
   data () {
diff --git a/front/src/components/audio/podcast/Row.vue b/front/src/components/audio/podcast/Row.vue
index 3b0bf43d2030771a583ebaaa6541902e3f183f21..06bec4855fdd5d3df73b3577f1379827eb2d8c08 100644
--- a/front/src/components/audio/podcast/Row.vue
+++ b/front/src/components/audio/podcast/Row.vue
@@ -105,7 +105,7 @@ export default {
     showDuration: { type: Boolean, required: false, default: true },
     index: { type: Number, required: true },
     track: { type: Object, required: true },
-    defaultCover: { type: Object, required: false, default: () => { return {} } }
+    defaultCover: { type: Object, required: false, default: null }
   },
 
   data () {
diff --git a/front/src/components/common/AttachmentInput.vue b/front/src/components/common/AttachmentInput.vue
index 72bc5264f22d3cb821f5839923e09396ad68015c..0a52dcbb26afdb057a8002a26be3679fa7ffa812 100644
--- a/front/src/components/common/AttachmentInput.vue
+++ b/front/src/components/common/AttachmentInput.vue
@@ -91,7 +91,7 @@ import axios from 'axios'
 
 export default {
   props: {
-    value: { type: String, required: true },
+    value: { type: String, default: null },
     imageClass: { type: String, default: '', required: false }
   },
   data () {
diff --git a/front/src/components/common/Duration.vue b/front/src/components/common/Duration.vue
index 81a0b578f1b827e0a6f2c21d0115170ff3f7614f..a1c652695e4e860ef3d14187ea045abbe90ee83b 100644
--- a/front/src/components/common/Duration.vue
+++ b/front/src/components/common/Duration.vue
@@ -16,7 +16,7 @@
 import { secondsToObject } from '@/filters'
 
 export default {
-  props: { seconds: { type: Number, required: true } },
+  props: { seconds: { type: Number, default: null } },
   computed: {
     durationData () {
       return secondsToObject(this.seconds)
diff --git a/front/src/components/common/RenderedDescription.vue b/front/src/components/common/RenderedDescription.vue
index 132faface084873745838820fc6462b7257d9546..2c4797350b849dd3e5e1ce2a0deec19dd9baf6e5 100644
--- a/front/src/components/common/RenderedDescription.vue
+++ b/front/src/components/common/RenderedDescription.vue
@@ -89,7 +89,7 @@ import clip from 'text-clipper'
 
 export default {
   props: {
-    content: { type: String, default: '' },
+    content: { type: Object, required: false, default: null },
     fieldName: { type: String, required: false, default: 'description' },
     updateUrl: { required: false, type: String, default: '' },
     canUpdate: { required: false, default: true, type: Boolean },
diff --git a/front/src/components/federation/LibraryWidget.vue b/front/src/components/federation/LibraryWidget.vue
index 9ba011015da8f49346269031dcbc44a0c4cf3b02..62683996af24844ad88ac65456504d1749fbccec 100644
--- a/front/src/components/federation/LibraryWidget.vue
+++ b/front/src/components/federation/LibraryWidget.vue
@@ -33,7 +33,7 @@
         :key="library.uuid"
         :display-scan="false"
         :display-follow="$store.state.auth.authenticated && library.actor.full_username != $store.state.auth.fullUsername"
-        :library="library"
+        :initial-library="library"
         :display-copy-fid="true"
       />
     </div>
diff --git a/front/src/components/forms/PasswordInput.vue b/front/src/components/forms/PasswordInput.vue
index 5b37bf60f99510b788dc1203eea057bef1d9bad6..b9dab0e4c4ab8f963acad33fe149699afe8b7115 100644
--- a/front/src/components/forms/PasswordInput.vue
+++ b/front/src/components/forms/PasswordInput.vue
@@ -33,7 +33,7 @@ export default {
     value: { type: String, required: true },
     defaultShow: { type: Boolean, default: false },
     copyButton: { type: Boolean, default: false },
-    fieldId: { type: Number, default: 0 }
+    fieldId: { type: String, required: true }
   },
   data () {
     return {
diff --git a/front/src/components/library/AlbumBase.vue b/front/src/components/library/AlbumBase.vue
index c8bf353494bcc1cb2542124e35806c0aeef26b69..9ed85eded99994f07c3a5fabe875db4491fc5122 100644
--- a/front/src/components/library/AlbumBase.vue
+++ b/front/src/components/library/AlbumBase.vue
@@ -273,7 +273,7 @@ export default {
     ArtistLabel,
     AlbumDropdown
   },
-  props: { id: { type: Number, required: true } },
+  props: { id: { type: [String, Number], required: true } },
   data () {
     return {
       isLoading: true,
diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue
index cbc7d4518f142af12a5b358db7af0f17e6f9b11b..95462c09dad33fddbb610f0a4eb5ec3c3d2a11b5 100644
--- a/front/src/components/library/ArtistBase.vue
+++ b/front/src/components/library/ArtistBase.vue
@@ -42,7 +42,7 @@
             <div class="ui buttons">
               <radio-button
                 type="artist"
-                :object-id="object.id"
+                :object-id="String(object.id)"
               />
             </div>
             <div class="ui buttons">
@@ -234,7 +234,7 @@ export default {
     TagsList
   },
   mixins: [ReportMixin],
-  props: { id: { type: Number, required: true } },
+  props: { id: { type: [String, Number], required: true } },
   data () {
     return {
       isLoading: true,
diff --git a/front/src/components/library/ArtistDetail.vue b/front/src/components/library/ArtistDetail.vue
index fff1a6183b9a594b069e245a608256fe02608a82..7f8248925a3c79afdf7dd6a625e93f35785c36b3 100644
--- a/front/src/components/library/ArtistDetail.vue
+++ b/front/src/components/library/ArtistDetail.vue
@@ -120,8 +120,8 @@ export default {
     tracks: { type: Array, required: true },
     albums: { type: Array, required: true },
     isLoadingAlbums: { type: Boolean, required: true },
-    nextTracksUrl: { type: String, required: true },
-    nextAlbumsUrl: { type: String, required: true }
+    nextTracksUrl: { type: String, default: null },
+    nextAlbumsUrl: { type: String, default: null }
   },
   data () {
     return {
diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue
index 60175df3d6f4508b87b2c3d4db3981032613882e..90c6b22b68f47e316424059a52bdf6baa5fd7d25 100644
--- a/front/src/components/library/EditForm.vue
+++ b/front/src/components/library/EditForm.vue
@@ -182,11 +182,11 @@
             </translate>
           </button>
         </template>
-        <div v-if="!lodash.isEqual(values[fieldConfig.id], initialValues[fieldConfig.id])">
+        <div v-if="fieldValuesChanged(fieldConfig.id)">
           <button
             class="ui tiny basic right floated reset button"
             form="noop"
-            @click.prevent="values[fieldConfig.id] = lodash.clone(initialValues[fieldConfig.id])"
+            @click.prevent="resetField(fieldConfig.id)"
           >
             <i class="undo icon" />
             <translate translate-context="Content/Library/Button.Label">
@@ -361,7 +361,14 @@ export default {
           self.isLoading = false
         }
       )
+    },
+    fieldValuesChanged (fieldId) {
+      return !_.isEqual(this.values[fieldId], this.initialValues[fieldId])
+    },
+    resetField (fieldId) {
+      this.values[fieldId] = _.clone(this.initialValues[fieldId])
     }
   }
+
 }
 </script>
diff --git a/front/src/components/library/TrackBase.vue b/front/src/components/library/TrackBase.vue
index 5107cc58a7a0c468ec0a6e7cdcda7a6e9c62712b..fd809c74ee4c48bc45224b968fe2eabffebb8d04 100644
--- a/front/src/components/library/TrackBase.vue
+++ b/front/src/components/library/TrackBase.vue
@@ -252,7 +252,7 @@ export default {
     EmbedWizard
   },
   mixins: [ReportMixin],
-  props: { id: { type: Number, required: true } },
+  props: { id: { type: [String, Number], required: true } },
   data () {
     return {
       time,
diff --git a/front/src/components/library/TrackDetail.vue b/front/src/components/library/TrackDetail.vue
index 182eba921d163ce4c7ea1a40c38f1bd25d0c2698..276edcba9f4891cf94cbd09c7941cacdda5c4a2d 100644
--- a/front/src/components/library/TrackDetail.vue
+++ b/front/src/components/library/TrackDetail.vue
@@ -301,7 +301,7 @@ export default {
   },
   props: {
     track: { type: Object, required: true },
-    libraries: { type: Array, required: true }
+    libraries: { type: Array, default: null }
   },
   data () {
     return {
diff --git a/front/src/components/library/TrackEdit.vue b/front/src/components/library/TrackEdit.vue
index add5c4485214259e2e5512d47ed5c7026e4e32f3..ce5422796bba857e9117533689fdb0c7d3b10ce3 100644
--- a/front/src/components/library/TrackEdit.vue
+++ b/front/src/components/library/TrackEdit.vue
@@ -53,7 +53,7 @@ export default {
   props: {
     objectType: { type: String, required: true },
     object: { type: Object, required: true },
-    libraries: { type: Array, required: true }
+    libraries: { type: Array, default: null }
   },
   data () {
     return {
diff --git a/front/src/components/moderation/ReportCategoryDropdown.vue b/front/src/components/moderation/ReportCategoryDropdown.vue
index abab26b27663cb5a65bbd9544d2d6c9852753f9a..e15bc50d29a6976166f891f611e2c3b60ccb6a26 100644
--- a/front/src/components/moderation/ReportCategoryDropdown.vue
+++ b/front/src/components/moderation/ReportCategoryDropdown.vue
@@ -30,11 +30,11 @@ import lodash from '@/lodash'
 export default {
   mixins: [TranslationsMixin],
   props: {
-    value: { type: String, required: true },
-    all: { type: String, required: true },
-    label: { type: String, required: true },
-    empty: { type: String, required: true },
-    required: { type: String, required: true },
+    value: { type: String, default: null },
+    all: { type: String, default: null },
+    label: { type: Boolean },
+    empty: { type: Boolean },
+    required: { type: Boolean },
     restrictTo: { type: Array, default: () => { return [] } }
   },
   computed: {
diff --git a/front/src/components/notifications/NotificationRow.vue b/front/src/components/notifications/NotificationRow.vue
index 3eaa16d965b1f151cfcf250a9875c759c550dc01..9668d5351a545ed36f8135f699556526203d31c3 100644
--- a/front/src/components/notifications/NotificationRow.vue
+++ b/front/src/components/notifications/NotificationRow.vue
@@ -107,7 +107,7 @@ export default {
           let acceptFollow = null
           let rejectFollow = null
           let message = null
-          if (a.related_object.approved === null) {
+          if (a.related_object && a.related_object.approved === null) {
             message = this.labels.libraryPendingFollowMessage
             acceptFollow = {
               buttonClass: 'success',
@@ -121,7 +121,7 @@ export default {
               label: this.$pgettext('Content/*/Button.Label/Verb', 'Reject'),
               handler: () => { self.rejectLibraryFollow(a.related_object) }
             }
-          } else if (a.related_object.approved) {
+          } else if (a.related_object && a.related_object.approved) {
             message = this.labels.libraryFollowMessage
           } else {
             message = this.labels.libraryRejectMessage
diff --git a/front/src/components/radios/Button.vue b/front/src/components/radios/Button.vue
index 107ea38663f603d03be86f2d6c7d31db5bbc5572..d3cead016e92926998e20e935eda8d6a00cd9982 100644
--- a/front/src/components/radios/Button.vue
+++ b/front/src/components/radios/Button.vue
@@ -25,10 +25,10 @@
 import lodash from '@/lodash'
 export default {
   props: {
-    customRadioId: { type: Number, default: 0, required: false },
+    customRadioId: { type: Number, required: false, default: null },
     type: { type: String, required: false, default: '' },
     clientOnly: { type: Boolean, default: false },
-    objectId: { type: String, default: null }
+    objectId: { type: [String, Number, Object], default: null }
   },
   computed: {
     running () {
diff --git a/front/src/components/radios/Card.vue b/front/src/components/radios/Card.vue
index 9d5cd182b44347ac31b41aae46235edd795dded3..d71c7d0b24159e32592b5fdbde07a1fbe8c5df3b 100644
--- a/front/src/components/radios/Card.vue
+++ b/front/src/components/radios/Card.vue
@@ -53,11 +53,11 @@ export default {
   props: {
     type: { type: String, required: true, default: '' },
     customRadio: { type: Object, required: false, default: () => { return {} } },
-    objectId: { type: String, required: false, default: '' }
+    objectId: { type: String, required: false, default: null }
   },
   computed: {
     radio () {
-      if (this.customRadio) {
+      if (Object.keys(this.customRadio).length > 0) {
         return this.customRadio
       }
       return this.$store.getters['radios/types'][this.type]
diff --git a/front/src/views/Notifications.vue b/front/src/views/Notifications.vue
index b97bedc71eb5f88ab382587fa89c6b810974f618..26bc12c3978cb90795b29ee0fd4810d00182e7e4 100644
--- a/front/src/views/Notifications.vue
+++ b/front/src/views/Notifications.vue
@@ -197,7 +197,7 @@
             <notification-row
               v-for="item in notifications.results"
               :key="item.id"
-              :item="item"
+              :initial-item="item"
             />
           </tbody>
         </table>
diff --git a/front/src/views/content/remote/Card.vue b/front/src/views/content/remote/Card.vue
index c66ed4d94005a8e616d265a0460bbee9cb9bef82..813ed48c0d3fecc97e806c02abbfb8c9837e104b 100644
--- a/front/src/views/content/remote/Card.vue
+++ b/front/src/views/content/remote/Card.vue
@@ -244,7 +244,7 @@ export default {
       isLoadingFollow: false,
       showScan: false,
       scanTimeout: null,
-      latestScan: this.library.latest_scan
+      latestScan: this.initialLibrary.latest_scan
     }
   },
   computed: {
diff --git a/front/src/views/library/DetailOverview.vue b/front/src/views/library/DetailOverview.vue
index 14db9f12ada7fa5af0bc2a8bea5a4616bdc07700..d66984814406f5a057b09b1a12daf94aac772d22 100644
--- a/front/src/views/library/DetailOverview.vue
+++ b/front/src/views/library/DetailOverview.vue
@@ -46,7 +46,7 @@ export default {
     ArtistWidget
   },
   props: {
-    object: { type: String, required: true },
+    object: { type: Object, required: true },
     isOwner: { type: Boolean, required: true }
   },
   data () {
diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue
index 272a0d535a57816be5e6d8d23d56ccb626d9420d..8cc6fa6d20fcad8741d73a2fa4c0d823b3538e8d 100644
--- a/front/src/views/playlists/Detail.vue
+++ b/front/src/views/playlists/Detail.vue
@@ -189,7 +189,7 @@ export default {
     EmbedWizard
   },
   props: {
-    id: { type: Number, required: true },
+    id: { type: [Number, String], required: true },
     defaultEdit: { type: Boolean, default: false }
   },
   data: function () {