diff --git a/CHANGELOG.md b/CHANGELOG.md
index 056a0881..123e5347 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,17 +3,35 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+
 ## [Unreleased]
+### Fixed
+- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
+- Fixed timeline errors locking timelines
+- Fixed missing highlighted border in expanded conversations
+
+### Changed
+- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers
+- Fixed custom emoji not working in profile field names
+- Fixed pinned statuses not appearing in user profiles
+
+
+## [2.2.1] - 2020-11-11
+### Fixed
+- Fixed regression in react popup alignment and overflowing
+
+
+## [2.2.0] - 2020-11-06
 ### Added
 - New option to optimize timeline rendering to make the site more responsive (enabled by default)
 - New instance option `logoLeft` to move logo to the left side in desktop nav bar
 - Import/export a muted users
 - Proper handling of deletes when using websocket streaming
 - Added optimistic chat message sending, so you can start writing next message before the previous one has been sent
+- Added a small red badge to the favicon when there's unread notifications
+- Added the NSFW alert to link previews
 
 ### Fixed
-- Fixed chats list not updating its order when new messages come in
-- Fixed chat messages sometimes getting lost when you receive a message at the same time
 - Fixed clicking NSFW hider through status popover
 - Fixed chat-view back button being hard to click
 - Fixed fresh chat notifications being cleared immediately while leaving the chat view and not having time to actually see the messages
@@ -29,6 +47,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Logo is now clickable
 - Changed default logo to SVG version
 
+
+## [2.1.2] - 2020-09-17
+### Fixed
+- Fixed chats list not updating its order when new messages come in
+- Fixed chat messages sometimes getting lost when you receive a message at the same time
+
+
 ## [2.1.1] - 2020-09-08
 ### Changed
 - Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
@@ -154,8 +179,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to change user's email
 - About page
 - Added remote user redirect
-- Bookmarks
+
 ### Changed
 - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
+
 ### Fixed
 - improved hotkey behavior on autocomplete popup
diff --git a/index.html b/index.html
index de75aedc..baf6d276 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,6 @@
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
-    <title>Pleroma</title>
     <!--server-generated-meta-->
     <link rel="icon" type="image/png" href="/favicon.png">
   </head>
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 3cbbf020..b472fcf6 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -7,6 +7,7 @@ import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
 import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
 import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
 import { applyTheme } from '../services/style_setter/style_setter.js'
+import FaviconService from '../services/favicon_service/favicon_service.js'
 
 let staticInitialResults = null
 
@@ -326,6 +327,8 @@ const afterStoreSetup = async ({ store, i18n }) => {
   const width = windowWidth()
   store.dispatch('setMobileLayout', width <= 800)
 
+  FaviconService.initFaviconService()
+
   const overrides = window.___pleromafe_dev_overrides || {}
   const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
   store.dispatch('setInstanceOption', { name: 'server', value: server })
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index e23fcb1b..3877b67f 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -8,14 +8,16 @@ import {
   faFile,
   faMusic,
   faImage,
-  faVideo
+  faVideo,
+  faPlayCircle
 } from '@fortawesome/free-solid-svg-icons'
 
 library.add(
   faFile,
   faMusic,
   faImage,
-  faVideo
+  faVideo,
+  faPlayCircle
 )
 
 const Attachment = {
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index e0b9fcc5..fd0dfa8f 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -57,13 +57,6 @@
   }
 
   &.-expanded {
-    .conversation-status {
-      border-color: $fallback--border;
-      border-color: var(--border, $fallback--border);
-      border-left-color: $fallback--cRed;
-      border-left-color: var(--cRed, $fallback--cRed);
-    }
-
     .conversation-status:last-child {
       border-bottom: none;
       border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js
index 444aafbe..add7c563 100644
--- a/src/components/link-preview/link-preview.js
+++ b/src/components/link-preview/link-preview.js
@@ -1,3 +1,5 @@
+import { mapGetters } from 'vuex'
+
 const LinkPreview = {
   name: 'LinkPreview',
   props: [
@@ -15,11 +17,20 @@ const LinkPreview = {
       // Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
       // as it makes sure to hide the image if somehow NSFW tagged preview can
       // exist.
-      return this.card.image && !this.nsfw && this.size !== 'hide'
+      return this.card.image && !this.censored && this.size !== 'hide'
+    },
+    censored () {
+      return this.nsfw && this.hideNsfwConfig
     },
     useDescription () {
       return this.card.description && /\S/.test(this.card.description)
-    }
+    },
+    hideNsfwConfig () {
+      return this.mergedConfig.hideNsfw
+    },
+    ...mapGetters([
+      'mergedConfig'
+    ])
   },
   created () {
     if (this.useImage) {
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
index 69171977..d3ca39b8 100644
--- a/src/components/link-preview/link-preview.vue
+++ b/src/components/link-preview/link-preview.vue
@@ -9,12 +9,17 @@
       <div
         v-if="useImage && imageLoaded"
         class="card-image"
-        :class="{ 'small-image': size === 'small' }"
       >
         <img :src="card.image">
       </div>
       <div class="card-content">
-        <span class="card-host faint">{{ card.provider_name }}</span>
+        <span class="card-host faint">
+          <span
+            v-if="censored"
+            class="nsfw-alert alert warning"
+          >{{ $t('status.nsfw') }}</span>
+          {{ card.provider_name }}
+        </span>
         <h4 class="card-title">{{ card.title }}</h4>
         <p
           v-if="useDescription"
@@ -50,10 +55,6 @@
     }
   }
 
-  .small-image {
-    width: 80px;
-  }
-
   .card-content {
     max-height: 100%;
     margin: 0.5em;
@@ -76,6 +77,10 @@
     max-height: calc(1.2em * 3 - 1px);
   }
 
+  .nsfw-alert {
+    margin: 2em 0;
+  }
+
   color: $fallback--text;
   color: var(--text, $fallback--text);
   border-style: solid;
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 4b479e13..49258563 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -6,6 +6,7 @@ import {
   filteredNotificationsFromStore,
   unseenNotificationsFromStore
 } from '../../services/notification_utils/notification_utils.js'
+import FaviconService from '../../services/favicon_service/favicon_service.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
 
@@ -75,8 +76,10 @@ const Notifications = {
   watch: {
     unseenCountTitle (count) {
       if (count > 0) {
+        FaviconService.drawFaviconBadge()
         this.$store.dispatch('setPageTitle', `(${count})`)
       } else {
+        FaviconService.clearFaviconBadge()
         this.$store.dispatch('setPageTitle', '')
       }
     }
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index bd875cca..b976026e 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -15,13 +15,6 @@
             class="badge badge-notification unseen-count"
           >{{ unseenCount }}</span>
         </div>
-        <div
-          v-if="error"
-          class="loadmore-error alert error"
-          @click.prevent
-        >
-          {{ $t('timeline.error_fetching') }}
-        </div>
         <button
           v-if="unseenCount"
           class="read-button"
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index c92a9f03..9d592165 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -533,7 +533,7 @@ const PostStatusForm = {
             !(isFormBiggerThanScroller &&
               this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
       const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
-      const targetScroll = currentScroll + totalDelta
+      const targetScroll = Math.round(currentScroll + totalDelta)
 
       if (scrollerRef === window) {
         scrollerRef.scroll(0, targetScroll)
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 95d95b11..e508a3e9 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -4,6 +4,7 @@
     placement="top"
     :offset="{ y: 5 }"
     class="react-button-popover"
+    :bound-to="{ x: 'container' }"
   >
     <div
       slot="content"
@@ -37,12 +38,13 @@
         <div class="reaction-bottom-fader" />
       </div>
     </div>
-    <FAIcon
-      slot="trigger"
-      class="fa-scale-110 fa-old-padding add-reaction-button"
-      :icon="['far', 'smile-beam']"
-      :title="$t('tool_tip.add_reaction')"
-    />
+    <span slot="trigger">
+      <FAIcon
+        class="fa-scale-110 fa-old-padding add-reaction-button"
+        :icon="['far', 'smile-beam']"
+        :title="$t('tool_tip.add_reaction')"
+      />
+    </span>
   </Popover>
 </template>
 
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 0a94de32..70c6d03d 100644
--- a/src/components/status/status.scss
+++ b/src/components/status/status.scss
@@ -29,6 +29,8 @@ $status-margin: 0.75em;
   &.-conversation {
     border-left-width: 4px;
     border-left-style: solid;
+    border-left-color: $fallback--cRed;
+    border-left-color: var(--cRed, $fallback--cRed);
   }
 
   .gravestone {
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index cba46daf..665d195e 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -50,17 +50,10 @@ const Timeline = {
     TimelineMenu
   },
   computed: {
-    timelineError () {
-      return this.$store.state.statuses.error
-    },
-    errorData () {
-      return this.$store.state.statuses.errorData
-    },
     newStatusCount () {
       return this.timeline.newStatusCount
     },
     showLoadButton () {
-      if (this.timelineError || this.errorData) return false
       return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0
     },
     loadButtonString () {
@@ -171,11 +164,12 @@ const Timeline = {
         userId: this.userId,
         tag: this.tag
       }).then(({ statuses }) => {
-        store.commit('setLoading', { timeline: this.timelineName, value: false })
         if (statuses && statuses.length === 0) {
           this.bottomedOut = true
         }
-      })
+      }).finally(() =>
+        store.commit('setLoading', { timeline: this.timelineName, value: false })
+      )
     }, 1000, this),
     determineVisibleStatuses () {
       if (!this.$refs.timeline) return
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 04859852..d4da2a87 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -2,22 +2,8 @@
   <div :class="[classes.root, 'Timeline']">
     <div :class="classes.header">
       <TimelineMenu v-if="!embedded" />
-      <div
-        v-if="timelineError"
-        class="loadmore-error alert error"
-        @click.prevent
-      >
-        {{ $t('timeline.error_fetching') }}
-      </div>
-      <div
-        v-else-if="errorData"
-        class="loadmore-error alert error"
-        @click.prevent
-      >
-        {{ errorData.statusText }}
-      </div>
       <button
-        v-else-if="showLoadButton"
+        v-if="showLoadButton"
         class="loadmore-button"
         @click.prevent="showNewStatuses"
       >
@@ -76,18 +62,12 @@
         {{ $t('timeline.no_more_statuses') }}
       </div>
       <a
-        v-else-if="!timeline.loading && !errorData"
+        v-else-if="!timeline.loading"
         href="#"
         @click.prevent="fetchOlderStatuses()"
       >
         <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
       </a>
-      <a
-        v-else-if="errorData"
-        href="#"
-      >
-        <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
-      </a>
       <div
         v-else
         class="new-status-notification text-center panel-footer"
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
index 4ccd52b4..ef8a5813 100644
--- a/src/components/timeline_menu/timeline_menu.js
+++ b/src/components/timeline_menu/timeline_menu.js
@@ -19,7 +19,7 @@ library.add(
   faChevronDown
 )
 
-// Route -> i18n key mapping, exported andnot in the computed
+// Route -> i18n key mapping, exported and not in the computed
 // because nav panel benefits from the same information.
 export const timelineNames = () => {
   return {
@@ -27,8 +27,7 @@ export const timelineNames = () => {
     'bookmarks': 'nav.bookmarks',
     'dms': 'nav.dms',
     'public-timeline': 'nav.public_tl',
-    'public-external-timeline': 'nav.twkn',
-    'tag-timeline': 'tag'
+    'public-external-timeline': 'nav.twkn'
   }
 }
 
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index f1f51840..745e795d 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -20,14 +20,13 @@
           :key="index"
           class="user-profile-field"
         >
+          <!-- eslint-disable vue/no-v-html -->
           <dt
             :title="user.fields_text[index].name"
             class="user-profile-field-name"
             @click.prevent="linkClicked"
-          >
-            {{ field.name }}
-          </dt>
-          <!-- eslint-disable vue/no-v-html -->
+            v-html="field.name"
+          />
           <dd
             :title="user.fields_text[index].value"
             class="user-profile-field-value"
diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue
index a4bf01e8..8a3ea1e3 100644
--- a/src/components/video_attachment/video_attachment.vue
+++ b/src/components/video_attachment/video_attachment.vue
@@ -1,6 +1,7 @@
 <template>
   <video
     class="video"
+    preload="metadata"
     :src="attachment.url"
     :loop="loopVideo"
     :controls="controls"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index dbd6a53f..762b907a 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -130,6 +130,7 @@
   },
   "notifications": {
     "broken_favorite": "Unknown status, searching for it…",
+    "error": "Error fetching notifications: {0}",
     "favorited_you": "favorited your status",
     "followed_you": "followed you",
     "follow_request": "wants to follow you",
@@ -377,7 +378,7 @@
     "hide_followers_count_description": "Don't show follower count",
     "show_admin_badge": "Show Admin badge in my profile",
     "show_moderator_badge": "Show Moderator badge in my profile",
-    "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+    "nsfw_clickthrough": "Enable clickthrough attachment and link preview image hiding for NSFW statuses",
     "oauth_tokens": "OAuth tokens",
     "token": "Token",
     "refresh_token": "Refresh Token",
@@ -635,7 +636,7 @@
   "timeline": {
     "collapse": "Collapse",
     "conversation": "Conversation",
-    "error_fetching": "Error fetching updates",
+    "error": "Error fetching timeline: {0}",
     "load_older": "Load older statuses",
     "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
     "repeated": "repeated",
@@ -667,7 +668,8 @@
     "hide_full_subject": "Hide full subject",
     "show_content": "Show content",
     "hide_content": "Hide content",
-    "status_deleted": "This post was deleted"
+    "status_deleted": "This post was deleted",
+    "nsfw": "NSFW"
   },
   "user_card": {
     "approve": "Approve",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 6a938f04..e68366ff 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -39,8 +39,7 @@ const emptyNotifications = () => ({
   minId: Number.POSITIVE_INFINITY,
   data: [],
   idStore: {},
-  loading: false,
-  error: false
+  loading: false
 })
 
 export const defaultState = () => ({
@@ -50,8 +49,6 @@ export const defaultState = () => ({
   maxId: 0,
   notifications: emptyNotifications(),
   favorites: new Set(),
-  error: false,
-  errorData: null,
   timelines: {
     mentions: emptyTl(),
     public: emptyTl(),
@@ -462,18 +459,9 @@ export const mutations = {
     const newStatus = state.allStatusesObject[id]
     newStatus.nsfw = nsfw
   },
-  setError (state, { value }) {
-    state.error = value
-  },
-  setErrorData (state, { value }) {
-    state.errorData = value
-  },
   setNotificationsLoading (state, { value }) {
     state.notifications.loading = value
   },
-  setNotificationsError (state, { value }) {
-    state.notifications.error = value
-  },
   setNotificationsSilence (state, { value }) {
     state.notifications.desktopNotificationSilence = value
   },
@@ -588,18 +576,9 @@ const statuses = {
       }
       commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects })
     },
-    setError ({ rootState, commit }, { value }) {
-      commit('setError', { value })
-    },
-    setErrorData ({ rootState, commit }, { value }) {
-      commit('setErrorData', { value })
-    },
     setNotificationsLoading ({ rootState, commit }, { value }) {
       commit('setNotificationsLoading', { value })
     },
-    setNotificationsError ({ rootState, commit }, { value }) {
-      commit('setNotificationsError', { value })
-    },
     setNotificationsSilence ({ rootState, commit }, { value }) {
       commit('setNotificationsSilence', { value })
     },
diff --git a/src/modules/users.js b/src/modules/users.js
index c4fc7e74..7018bbd8 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -137,11 +137,11 @@ export const mutations = {
   },
   saveFriendIds (state, { id, friendIds }) {
     const user = state.usersObject[id]
-    user.friendIds = uniq(concat(user.friendIds, friendIds))
+    user.friendIds = uniq(concat(user.friendIds || [], friendIds))
   },
   saveFollowerIds (state, { id, followerIds }) {
     const user = state.usersObject[id]
-    user.followerIds = uniq(concat(user.followerIds, followerIds))
+    user.followerIds = uniq(concat(user.followerIds || [], followerIds))
   },
   // Because frontend doesn't have a reason to keep these stuff in memory
   // outside of viewing someones user profile.
@@ -202,7 +202,9 @@ export const mutations = {
   },
   setPinnedToUser (state, status) {
     const user = state.usersObject[status.user.id]
+    user.pinnedStatusIds = user.pinnedStatusIds || []
     const index = user.pinnedStatusIds.indexOf(status.id)
+
     if (status.pinned && index === -1) {
       user.pinnedStatusIds.push(status.id)
     } else if (!status.pinned && index !== -1) {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 22b5e8ba..8da933c4 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -560,7 +560,7 @@ const fetchTimeline = ({
     })
     .then((data) => data.json())
     .then((data) => {
-      if (!data.error) {
+      if (!data.errors) {
         return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination }
       } else {
         data.status = status
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index e423eb4a..62355022 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -2,6 +2,15 @@ import escape from 'escape-html'
 import parseLinkHeader from 'parse-link-header'
 import { isStatusNotification } from '../notification_utils/notification_utils.js'
 
+/** NOTICE! **
+ * Do not initialize UI-generated data here.
+ * It will override existing data.
+ *
+ * i.e. user.pinnedStatusIds was set to [] here
+ * UI code would update it with data but upon next user fetch
+ * it would be reverted back to []
+ */
+
 const qvitterStatusType = (status) => {
   if (status.is_post_verb) {
     return 'status'
@@ -53,7 +62,7 @@ export const parseUser = (data) => {
     output.fields = data.fields
     output.fields_html = data.fields.map(field => {
       return {
-        name: addEmojis(field.name, data.emojis),
+        name: addEmojis(escape(field.name), data.emojis),
         value: addEmojis(field.value, data.emojis)
       }
     })
@@ -173,9 +182,6 @@ export const parseUser = (data) => {
   output.locked = data.locked
   output.followers_count = data.followers_count
   output.statuses_count = data.statuses_count
-  output.friendIds = []
-  output.followerIds = []
-  output.pinnedStatusIds = []
 
   if (data.pleroma) {
     output.follow_request_count = data.pleroma.follow_request_count
diff --git a/src/services/favicon_service/favicon_service.js b/src/services/favicon_service/favicon_service.js
new file mode 100644
index 00000000..d1ddee41
--- /dev/null
+++ b/src/services/favicon_service/favicon_service.js
@@ -0,0 +1,61 @@
+import { find } from 'lodash'
+
+const createFaviconService = () => {
+  let favimg, favcanvas, favcontext, favicon
+  const faviconWidth = 128
+  const faviconHeight = 128
+  const badgeRadius = 32
+
+  const initFaviconService = () => {
+    const nodes = document.getElementsByTagName('link')
+    favicon = find(nodes, node => node.rel === 'icon')
+    if (favicon) {
+      favcanvas = document.createElement('canvas')
+      favcanvas.width = faviconWidth
+      favcanvas.height = faviconHeight
+      favimg = new Image()
+      favimg.src = favicon.href
+      favcontext = favcanvas.getContext('2d')
+    }
+  }
+
+  const isImageLoaded = (img) => img.complete && img.naturalHeight !== 0
+
+  const clearFaviconBadge = () => {
+    if (!favimg || !favcontext || !favicon) return
+
+    favcontext.clearRect(0, 0, faviconWidth, faviconHeight)
+    if (isImageLoaded(favimg)) {
+      favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight)
+    }
+    favicon.href = favcanvas.toDataURL('image/png')
+  }
+
+  const drawFaviconBadge = () => {
+    if (!favimg || !favcontext || !favcontext) return
+
+    clearFaviconBadge()
+
+    const style = getComputedStyle(document.body)
+    const badgeColor = `${style.getPropertyValue('--badgeNotification') || 'rgb(240, 100, 100)'}`
+
+    if (isImageLoaded(favimg)) {
+      favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight)
+    }
+    favcontext.fillStyle = badgeColor
+    favcontext.beginPath()
+    favcontext.arc(faviconWidth - badgeRadius, badgeRadius, badgeRadius, 0, 2 * Math.PI, false)
+    favcontext.fill()
+    favicon.href = favcanvas.toDataURL('image/png')
+  }
+
+  return {
+    initFaviconService,
+    clearFaviconBadge,
+    drawFaviconBadge
+  }
+}
+
+const FaviconService = createFaviconService()
+
+export default FaviconService
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index c908b644..beeb167c 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -2,7 +2,6 @@ import apiService from '../api/api.service.js'
 import { promiseInterval } from '../promise_interval/promise_interval.js'
 
 const update = ({ store, notifications, older }) => {
-  store.dispatch('setNotificationsError', { value: false })
   store.dispatch('addNewNotifications', { notifications, older })
 }
 
@@ -47,11 +46,22 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
 
 const fetchNotifications = ({ store, args, older }) => {
   return apiService.fetchTimeline(args)
-    .then(({ data: notifications }) => {
+    .then((response) => {
+      if (response.errors) {
+        throw new Error(`${response.status} ${response.statusText}`)
+      }
+      const notifications = response.data
       update({ store, notifications, older })
       return notifications
-    }, () => store.dispatch('setNotificationsError', { value: true }))
-    .catch(() => store.dispatch('setNotificationsError', { value: true }))
+    })
+    .catch((error) => {
+      store.dispatch('pushGlobalNotice', {
+        level: 'error',
+        messageKey: 'notifications.error',
+        messageArgs: [error.message],
+        timeout: 5000
+      })
+    })
 }
 
 const startFetching = ({ credentials, store }) => {
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index 72ea4890..921df3ed 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -6,9 +6,6 @@ import { promiseInterval } from '../promise_interval/promise_interval.js'
 const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => {
   const ccTimeline = camelCase(timeline)
 
-  store.dispatch('setError', { value: false })
-  store.dispatch('setErrorData', { value: null })
-
   store.dispatch('addNewStatuses', {
     timeline: ccTimeline,
     userId,
@@ -52,9 +49,8 @@ const fetchAndUpdate = ({
 
   return apiService.fetchTimeline(args)
     .then(response => {
-      if (response.error) {
-        store.dispatch('setErrorData', { value: response })
-        return
+      if (response.errors) {
+        throw new Error(`${response.status} ${response.statusText}`)
       }
 
       const { data: statuses, pagination } = response
@@ -63,7 +59,15 @@ const fetchAndUpdate = ({
       }
       update({ store, statuses, timeline, showImmediately, userId, pagination })
       return { statuses, pagination }
-    }, () => store.dispatch('setError', { value: true }))
+    })
+    .catch((error) => {
+      store.dispatch('pushGlobalNotice', {
+        level: 'error',
+        messageKey: 'timeline.error',
+        messageArgs: [error.message],
+        timeout: 5000
+      })
+    })
 }
 
 const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => {
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index dcf066f9..80092b41 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -8,8 +8,7 @@ const localVue = createLocalVue()
 localVue.use(Vuex)
 
 const mutations = {
-  clearTimeline: () => {},
-  setError: () => {}
+  clearTimeline: () => {}
 }
 
 const actions = {
