diff --git a/background/update.js b/background/update.js
index fcd238e6..eb08d128 100644
--- a/background/update.js
+++ b/background/update.js
@@ -19,10 +19,18 @@ var updater = {
   SAME_VERSION: 'up-to-date: version is unchanged',
   ERROR_MD5: 'error: MD5 is invalid',
   ERROR_JSON: 'error: JSON is invalid',
-  ERROR_VERSION: 'error: version is invalid',
+  ERROR_VERSION: 'error: version is older than installed style',
 
   lastUpdateTime: parseInt(localStorage.lastUpdateTime) || Date.now(),
 
+  isSame(code) {
+    return code === updater.SAME_MD5 || code === updater.SAME_CODE || code === updater.SAME_VERSION;
+  },
+
+  isEdited(code) {
+    return code === updater.EDITED || code === updater.MAYBE_EDITED;
+  },
+
   checkAllStyles({observer = () => {}, save = true, ignoreDigest} = {}) {
     updater.resetInterval();
     updater.checkAllStyles.running = true;
@@ -72,11 +80,12 @@ var updater = {
       });
 
     function checkIfEdited(digest) {
-      if (style.usercss) {
-        // FIXME: remove this after we can calculate digest from style.source
+      if (ignoreDigest) {
         return;
       }
-      if (!ignoreDigest && style.originalDigest && style.originalDigest !== digest) {
+      if (style.usercss && style.edited) {
+        return Promise.reject(updater.EDITED);
+      } else if (style.originalDigest && style.originalDigest !== digest) {
         return Promise.reject(updater.EDITED);
       }
     }
@@ -97,34 +106,33 @@ var updater = {
     function maybeUpdateUsercss() {
       return download(style.updateUrl).then(text => {
         const json = usercss.buildMeta(text);
-        if (!json.version) {
+        // re-install is invalid in a soft upgrade
+        if (semverCompare(style.version, json.version) === 0 && !ignoreDigest) {
+          return Promise.reject(updater.SAME_VERSION);
+        }
+        // downgrade is always invalid
+        if (semverCompare(style.version, json.version) > 0) {
           return Promise.reject(updater.ERROR_VERSION);
         }
-        if (style.version) {
-          if (semverCompare(style.version, json.version) === 0) {
-            return Promise.reject(updater.SAME_VERSION);
-          }
-          if (semverCompare(style.version, json.version) > 0) {
-            return Promise.reject(updater.ERROR_VERSION);
-          }
-        }
-        json.id = style.id;
         return json;
       });
     }
 
     function maybeSave(json) {
-      if (!styleJSONseemsValid(json)) {
-        return Promise.reject(updater.ERROR_JSON);
-      }
       json.id = style.id;
-      if (styleSectionsEqual(json, style)) {
-        // JSONs may have different order of items even if sections are effectively equal
-        // so we'll update the digest anyway
-        saveStyle(Object.assign(json, {reason: 'update-digest'}));
-        return Promise.reject(updater.SAME_CODE);
-      } else if (!style.originalDigest && !ignoreDigest) {
-        return Promise.reject(updater.MAYBE_EDITED);
+      // no need to compare section code for usercss, they are built dynamically
+      if (!json.usercss) {
+        if (!styleJSONseemsValid(json)) {
+          return Promise.reject(updater.ERROR_JSON);
+        }
+        if (styleSectionsEqual(json, style)) {
+          // JSONs may have different order of items even if sections are effectively equal
+          // so we'll update the digest anyway
+          saveStyle(Object.assign(json, {reason: 'update-digest'}));
+          return Promise.reject(updater.SAME_CODE);
+        } else if (!style.originalDigest && !ignoreDigest) {
+          return Promise.reject(updater.MAYBE_EDITED);
+        }
       }
       return !save ? json :
         saveStyle(Object.assign(json, {
diff --git a/edit/source-editor.js b/edit/source-editor.js
index 2fbc2188..77e4e478 100644
--- a/edit/source-editor.js
+++ b/edit/source-editor.js
@@ -111,6 +111,7 @@ function createSourceEditor(style) {
       reason: 'editSave',
       id: style.id,
       enabled: style.enabled,
+      edited: dirty.has('source'),
       source: style.source
     };
     return onBackgroundReady().then(() => BG.saveUsercss(req))
diff --git a/edit/util.js b/edit/util.js
index e76e289e..d5291fcf 100644
--- a/edit/util.js
+++ b/edit/util.js
@@ -86,5 +86,9 @@ function dirtyReporter() {
     };
   }
 
-  return wrap({add, remove, modify, clear, isDirty, onChange});
+  function has(key) {
+    return dirty.has(key);
+  }
+
+  return wrap({add, remove, modify, clear, isDirty, onChange, has});
 }
diff --git a/js/usercss.js b/js/usercss.js
index 5099e8b0..894a7219 100644
--- a/js/usercss.js
+++ b/js/usercss.js
@@ -173,6 +173,7 @@ var usercss = (function () {
       usercss: true,
       version: null,
       source: source,
+      edited: false,
       enabled: true,
       sections: [],
       vars: {},
diff --git a/manage/updater-ui.js b/manage/updater-ui.js
index a17a170b..358d9a63 100644
--- a/manage/updater-ui.js
+++ b/manage/updater-ui.js
@@ -114,8 +114,8 @@ function reportUpdateState(state, style, details) {
       if (entry.classList.contains('can-update')) {
         break;
       }
-      const same = details === BG.updater.SAME_MD5 || details === BG.updater.SAME_CODE;
-      const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED;
+      const same = BG.updater.isSame(details);
+      const edited = BG.updater.isEdited(details);
       entry.dataset.details = details;
       if (!details) {
         details = t('updateCheckFailServerUnreachable');