diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js
index b2a8c7f3e1fa..2124147687e4 100644
--- a/src/webui/www/private/scripts/client.js
+++ b/src/webui/www/private/scripts/client.js
@@ -46,18 +46,18 @@ let clipboardEvent;
 const CATEGORIES_ALL = 1;
 const CATEGORIES_UNCATEGORIZED = 2;
 
-let category_list = {};
+const category_list = new Map();
 
-let selected_category = CATEGORIES_ALL;
+let selected_category = Number(LocalPreferences.get('selected_category', CATEGORIES_ALL));
 let setCategoryFilter = function() {};
 
 /* Tags filter */
 const TAGS_ALL = 1;
 const TAGS_UNTAGGED = 2;
 
-let tagList = {};
+const tagList = new Map();
 
-let selectedTag = TAGS_ALL;
+let selectedTag = Number(LocalPreferences.get('selected_tag', TAGS_ALL));
 let setTagFilter = function() {};
 
 /* Trackers filter */
@@ -74,16 +74,6 @@ let selected_filter = LocalPreferences.get('selected_filter', 'all');
 let setFilter = function() {};
 let toggleFilterDisplay = function() {};
 
-const loadSelectedCategory = function() {
-    selected_category = LocalPreferences.get('selected_category', CATEGORIES_ALL);
-};
-loadSelectedCategory();
-
-const loadSelectedTag = function() {
-    selectedTag = LocalPreferences.get('selected_tag', TAGS_ALL);
-};
-loadSelectedTag();
-
 const loadSelectedTracker = function() {
     selectedTracker = LocalPreferences.get('selected_tracker', TRACKERS_ALL);
 };
@@ -244,7 +234,7 @@ window.addEvent('load', function() {
     };
 
     setTagFilter = function(hash) {
-        selectedTag = hash.toString();
+        selectedTag = hash;
         LocalPreferences.set('selected_tag', selectedTag);
         highlightSelectedTag();
         if (torrentsTable.tableBody !== undefined)
@@ -358,14 +348,11 @@ window.addEvent('load', function() {
             return false;
 
         let removed = false;
-        for (const key in category_list) {
-            if (!Object.hasOwn(category_list, key))
-                continue;
-
-            const category = category_list[key];
+        category_list.forEach((category) => {
             const deleteResult = category.torrents.delete(hash);
             removed ||= deleteResult;
-        }
+        });
+
         return removed;
     };
 
@@ -379,16 +366,19 @@ window.addEvent('load', function() {
             removeTorrentFromCategoryList(hash);
             return true;
         }
+
         const categoryHash = genHash(category);
-        if (!category_list[categoryHash]) { // This should not happen
-            category_list[categoryHash] = {
+        if (!category_list.has(categoryHash)) { // This should not happen
+            category_list.set(categoryHash, {
                 name: category,
-                torrents: []
-            };
+                torrents: new Set()
+            });
         }
-        if (!category_list[categoryHash].torrents.has(hash)) {
+
+        const torrents = category_list.get(categoryHash).torrents;
+        if (!torrents.has(hash)) {
             removeTorrentFromCategoryList(hash);
-            category_list[categoryHash].torrents.add(hash);
+            torrents.add(hash);
             return true;
         }
         return false;
@@ -399,14 +389,11 @@ window.addEvent('load', function() {
             return false;
 
         let removed = false;
-        for (const key in tagList) {
-            if (!Object.hasOwn(tagList, key))
-                continue;
-
-            const tag = tagList[key];
+        tagList.forEach((tag) => {
             const deleteResult = tag.torrents.delete(hash);
             removed ||= deleteResult;
-        }
+        });
+
         return removed;
     };
 
@@ -424,14 +411,16 @@ window.addEvent('load', function() {
         let added = false;
         for (let i = 0; i < tags.length; ++i) {
             const tagHash = genHash(tags[i].trim());
-            if (!tagList[tagHash]) { // This should not happen
-                tagList[tagHash] = {
+            if (!tagList.has(tagHash)) { // This should not happen
+                tagList.set(tagHash, {
                     name: tags,
                     torrents: new Set()
-                };
+                });
             }
-            if (!tagList[tagHash].torrents.has(hash)) {
-                tagList[tagHash].torrents.add(hash);
+
+            const torrents = tagList.get(tagHash).torrents;
+            if (!torrents.has(hash)) {
+                torrents.add(hash);
                 added = true;
             }
         }
@@ -474,7 +463,7 @@ window.addEvent('load', function() {
                 margin_left = (category_path.length - 1) * 20;
             }
 
-            const html = '<a href="#" style="margin-left: ' + margin_left + 'px" onclick="setCategoryFilter(' + hash + ');return false;">'
+            const html = `<a href="#" style="margin-left: ${margin_left}px;" onclick="setCategoryFilter(${hash}); return false;">`
                 + '<img src="images/view-categories.svg"/>'
                 + window.qBittorrent.Misc.escapeHtml(display_name) + ' (' + count + ')' + '</a>';
             const el = new Element('li', {
@@ -487,20 +476,26 @@ window.addEvent('load', function() {
 
         const all = torrentsTable.getRowIds().length;
         let uncategorized = 0;
-        Object.each(torrentsTable.rows, function(row) {
+        for (const key in torrentsTable.rows) {
+            if (!Object.hasOwn(torrentsTable.rows, key))
+                continue;
+
+            const row = torrentsTable.rows[key];
             if (row['full_data'].category.length === 0)
                 uncategorized += 1;
-        });
+        }
         categoryList.appendChild(create_link(CATEGORIES_ALL, 'QBT_TR(All)QBT_TR[CONTEXT=CategoryFilterModel]', all));
         categoryList.appendChild(create_link(CATEGORIES_UNCATEGORIZED, 'QBT_TR(Uncategorized)QBT_TR[CONTEXT=CategoryFilterModel]', uncategorized));
 
         const sortedCategories = [];
-        Object.each(category_list, function(category) {
-            sortedCategories.push(category.name);
-        });
-        sortedCategories.sort((leftCategory, rightCategory) => {
-            const leftSegments = leftCategory.split('/');
-            const rightSegments = rightCategory.split('/');
+        category_list.forEach((category, hash) => sortedCategories.push({
+            categoryName: category.name,
+            categoryHash: hash,
+            categoryCount: category.torrents.size
+        }));
+        sortedCategories.sort((left, right) => {
+            const leftSegments = left.categoryName.split('/');
+            const rightSegments = right.categoryName.split('/');
 
             for (let i = 0, iMax = Math.min(leftSegments.length, rightSegments.length); i < iMax; ++i) {
                 const compareResult = window.qBittorrent.Misc.naturalSortCollator.compare(
@@ -513,15 +508,13 @@ window.addEvent('load', function() {
         });
 
         for (let i = 0; i < sortedCategories.length; ++i) {
-            const categoryName = sortedCategories[i];
-            const categoryHash = genHash(categoryName);
-            let categoryCount = category_list[categoryHash].torrents.size;
+            const { categoryName, categoryHash } = sortedCategories[i];
+            let { categoryCount } = sortedCategories[i];
 
             if (useSubcategories) {
                 for (let j = (i + 1);
-                    (j < sortedCategories.length) && sortedCategories[j].startsWith(categoryName + "/"); ++j) {
-                    const hash = genHash(sortedCategories[j]);
-                    categoryCount += category_list[hash].torrents.size;
+                    ((j < sortedCategories.length) && sortedCategories[j].categoryName.startsWith(categoryName + "/")); ++j) {
+                    categoryCount += sortedCategories[j].categoryCount;
                 }
             }
 
@@ -537,7 +530,7 @@ window.addEvent('load', function() {
             return;
         const children = categoryList.childNodes;
         for (let i = 0; i < children.length; ++i) {
-            if (children[i].id == selected_category)
+            if (Number(children[i].id) === selected_category)
                 children[i].className = "selectedFilter";
             else
                 children[i].className = "";
@@ -552,7 +545,7 @@ window.addEvent('load', function() {
         tagFilterList.getChildren().each(c => c.destroy());
 
         const createLink = function(hash, text, count) {
-            const html = '<a href="#" onclick="setTagFilter(' + hash + ');return false;">'
+            const html = `<a href="#" onclick="setTagFilter(${hash}); return false;">`
                 + '<img src="images/tags.svg"/>'
                 + window.qBittorrent.Misc.escapeHtml(text) + ' (' + count + ')' + '</a>';
             const el = new Element('li', {
@@ -573,16 +566,15 @@ window.addEvent('load', function() {
         tagFilterList.appendChild(createLink(TAGS_UNTAGGED, 'QBT_TR(Untagged)QBT_TR[CONTEXT=TagFilterModel]', untagged));
 
         const sortedTags = [];
-        for (const key in tagList)
-            sortedTags.push(tagList[key].name);
-        sortedTags.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
-
-        for (let i = 0; i < sortedTags.length; ++i) {
-            const tagName = sortedTags[i];
-            const tagHash = genHash(tagName);
-            const tagCount = tagList[tagHash].torrents.size;
-            tagFilterList.appendChild(createLink(tagHash, tagName, tagCount));
-        }
+        tagList.forEach((tag, hash) => sortedTags.push({
+            tagName: tag.name,
+            tagHash: hash,
+            tagSize: tag.torrents.size
+        }));
+        sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName));
+
+        for (const { tagName, tagHash, tagSize } of sortedTags)
+            tagFilterList.appendChild(createLink(tagHash, tagName, tagSize));
 
         highlightSelectedTag();
     };
@@ -594,7 +586,7 @@ window.addEvent('load', function() {
 
         const children = tagFilterList.childNodes;
         for (let i = 0; i < children.length; ++i)
-            children[i].className = (children[i].id === selectedTag) ? "selectedFilter" : "";
+            children[i].className = (Number(children[i].id) === selectedTag) ? "selectedFilter" : "";
     };
 
     const updateTrackerList = function() {
@@ -626,14 +618,15 @@ window.addEvent('load', function() {
         trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, 'QBT_TR(Trackerless (%1))QBT_TR[CONTEXT=TrackerFiltersList]', trackerlessTorrentsCount));
 
         // Sort trackers by hostname
-        const sortedList = [...trackerList.entries()].sort((left, right) => {
-            const leftHost = getHost(left[1].url);
-            const rightHost = getHost(right[1].url);
-            return window.qBittorrent.Misc.naturalSortCollator.compare(leftHost, rightHost);
-        });
-        for (const [hash, tracker] of sortedList) {
-            trackerFilterList.appendChild(createLink(hash, (getHost(tracker.url) + ' (%1)'), tracker.torrents.length));
-        }
+        const sortedList = [];
+        trackerList.forEach((tracker, hash) => sortedList.push({
+            trackerHost: getHost(tracker.url),
+            trackerHash: hash,
+            trackerCount: tracker.torrents.length
+        }));
+        sortedList.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.trackerHost, right.trackerHost));
+        for (const { trackerHost, trackerHash, trackerCount } of sortedList)
+            trackerFilterList.appendChild(createLink(trackerHash, (trackerHost + ' (%1)'), trackerCount));
 
         highlightSelectedTracker();
     };
@@ -675,26 +668,30 @@ window.addEvent('load', function() {
                     if (full_update) {
                         torrentsTableSelectedRows = torrentsTable.selectedRowsIds();
                         torrentsTable.clear();
-                        category_list = {};
-                        tagList = {};
+                        category_list.clear();
+                        tagList.clear();
                     }
                     if (response['rid']) {
                         syncMainDataLastResponseId = response['rid'];
                     }
                     if (response['categories']) {
                         for (const key in response['categories']) {
-                            const category = response['categories'][key];
+                            if (!Object.hasOwn(response['categories'], key))
+                                continue;
+
+                            const responseCategory = response['categories'][key];
                             const categoryHash = genHash(key);
-                            if (category_list[categoryHash] !== undefined) {
+                            const category = category_list.get(categoryHash);
+                            if (category !== undefined) {
                                 // only the save path can change for existing categories
-                                category_list[categoryHash].savePath = category.savePath;
+                                category.savePath = responseCategory.savePath;
                             }
                             else {
-                                category_list[categoryHash] = {
-                                    name: category.name,
-                                    savePath: category.savePath,
+                                category_list.set(categoryHash, {
+                                    name: responseCategory.name,
+                                    savePath: responseCategory.savePath,
                                     torrents: new Set()
-                                };
+                                });
                             }
                         }
                         update_categories = true;
@@ -702,18 +699,18 @@ window.addEvent('load', function() {
                     if (response['categories_removed']) {
                         response['categories_removed'].each(function(category) {
                             const categoryHash = genHash(category);
-                            delete category_list[categoryHash];
+                            category_list.delete(categoryHash);
                         });
                         update_categories = true;
                     }
                     if (response['tags']) {
                         for (const tag of response['tags']) {
                             const tagHash = genHash(tag);
-                            if (!tagList[tagHash]) {
-                                tagList[tagHash] = {
+                            if (!tagList.has(tagHash)) {
+                                tagList.set(tagHash, {
                                     name: tag,
                                     torrents: new Set()
-                                };
+                                });
                             }
                         }
                         updateTags = true;
@@ -721,7 +718,7 @@ window.addEvent('load', function() {
                     if (response['tags_removed']) {
                         for (let i = 0; i < response['tags_removed'].length; ++i) {
                             const tagHash = genHash(response['tags_removed'][i]);
-                            delete tagList[tagHash];
+                            tagList.delete(tagHash);
                         }
                         updateTags = true;
                     }
diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js
index 70e7fea0654a..c03f09e4ea5f 100644
--- a/src/webui/www/private/scripts/contextmenu.js
+++ b/src/webui/www/private/scripts/contextmenu.js
@@ -311,10 +311,10 @@ window.qBittorrent.ContextMenu = (function() {
             let all_are_super_seeding = true;
             let all_are_auto_tmm = true;
             let there_are_auto_tmm = false;
-            const tagsSelectionState = Object.clone(tagList);
+            const tagCount = new Map();
 
-            const h = torrentsTable.selectedRowsIds();
-            h.each(function(item, index) {
+            const selectedRows = torrentsTable.selectedRowsIds();
+            selectedRows.forEach((item, index) => {
                 const data = torrentsTable.rows.get(item).full_data;
 
                 if (data['seq_dl'] !== true)
@@ -348,23 +348,15 @@ window.qBittorrent.ContextMenu = (function() {
                     all_are_auto_tmm = false;
 
                 const torrentTags = data['tags'].split(', ');
-                for (const key in tagsSelectionState) {
-                    const tag = tagsSelectionState[key];
-                    const tagExists = torrentTags.contains(tag.name);
-                    if ((tag.checked !== undefined) && (tag.checked != tagExists))
-                        tag.indeterminate = true;
-                    if (tag.checked === undefined)
-                        tag.checked = tagExists;
-                    else
-                        tag.checked = tag.checked && tagExists;
+                for (const tag of torrentTags) {
+                    const count = tagCount.get(tag);
+                    tagCount.set(tag, ((count !== undefined) ? (count + 1) : 1));
                 }
             });
 
-            let show_seq_dl = true;
-
             // hide renameFiles when more than 1 torrent is selected
-            if (h.length == 1) {
-                const data = torrentsTable.rows.get(h[0]).full_data;
+            if (selectedRows.length == 1) {
+                const data = torrentsTable.rows.get(selectedRows[0]).full_data;
                 let metadata_downloaded = !(data['state'] == 'metaDL' || data['state'] == 'forcedMetaDL' || data['total_size'] == -1);
 
                 // hide renameFiles when metadata hasn't been downloaded yet
@@ -372,16 +364,9 @@ window.qBittorrent.ContextMenu = (function() {
                     ? this.showItem('renameFiles')
                     : this.hideItem('renameFiles');
             }
-            else
+            else {
                 this.hideItem('renameFiles');
-
-            if (!all_are_seq_dl && there_are_seq_dl)
-                show_seq_dl = false;
-
-            let show_f_l_piece_prio = true;
-
-            if (!all_are_f_l_piece_prio && there_are_f_l_piece_prio)
-                show_f_l_piece_prio = false;
+            }
 
             if (all_are_downloaded) {
                 this.hideItem('downloadLimit');
@@ -392,6 +377,9 @@ window.qBittorrent.ContextMenu = (function() {
                 this.setItemChecked('superSeeding', all_are_super_seeding);
             }
             else {
+                const show_seq_dl = (all_are_seq_dl || !there_are_seq_dl);
+                const show_f_l_piece_prio = (all_are_f_l_piece_prio || !there_are_f_l_piece_prio);
+
                 if (!show_seq_dl && show_f_l_piece_prio)
                     this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.addClass('separator');
                 else
@@ -434,42 +422,45 @@ window.qBittorrent.ContextMenu = (function() {
             }
 
             const contextTagList = $('contextTagList');
-            for (const tagHash in tagList) {
-                const checkbox = contextTagList.getElement('a[href=#Tag/' + tagHash + '] input[type=checkbox]');
-                const checkboxState = tagsSelectionState[tagHash];
-                checkbox.indeterminate = checkboxState.indeterminate;
-                checkbox.checked = checkboxState.checked;
-            }
+            tagList.forEach((tag, tagHash) => {
+                const checkbox = contextTagList.getElement(`a[href="#Tag/${tagHash}"] input[type="checkbox"]`);
+                const count = tagCount.get(tag.name);
+                const hasCount = (count !== undefined);
+                const isLesser = (count < selectedRows.length);
+                checkbox.indeterminate = (hasCount ? isLesser : false);
+                checkbox.checked = (hasCount ? !isLesser : false);
+            });
         },
 
-        updateCategoriesSubMenu: function(category_list) {
-            const categoryList = $('contextCategoryList');
-            categoryList.getChildren().each(c => c.destroy());
-            categoryList.appendChild(new Element('li', {
+        updateCategoriesSubMenu: function(categoryList) {
+            const contextCategoryList = $('contextCategoryList');
+            contextCategoryList.getChildren().each(c => c.destroy());
+            contextCategoryList.appendChild(new Element('li', {
                 html: '<a href="javascript:torrentNewCategoryFN();"><img src="images/list-add.svg" alt="QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]"/> QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]</a>'
             }));
-            categoryList.appendChild(new Element('li', {
+            contextCategoryList.appendChild(new Element('li', {
                 html: '<a href="javascript:torrentSetCategoryFN(0);"><img src="images/edit-clear.svg" alt="QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]"/> QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]</a>'
             }));
 
             const sortedCategories = [];
-            Object.each(category_list, function(category) {
-                sortedCategories.push(category.name);
-            });
-            sortedCategories.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
+            categoryList.forEach((category, hash) => sortedCategories.push({
+                categoryName: category.name,
+                categoryHash: hash
+            }));
+            sortedCategories.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(
+                left.categoryName, right.categoryName));
 
             let first = true;
-            Object.each(sortedCategories, function(categoryName) {
-                const categoryHash = genHash(categoryName);
+            for (const { categoryName, categoryHash } of sortedCategories) {
                 const el = new Element('li', {
-                    html: '<a href="javascript:torrentSetCategoryFN(\'' + categoryHash + '\');"><img src="images/view-categories.svg"/> ' + window.qBittorrent.Misc.escapeHtml(categoryName) + '</a>'
+                    html: `<a href="javascript:torrentSetCategoryFN(${categoryHash});"><img src="images/view-categories.svg"/>${window.qBittorrent.Misc.escapeHtml(categoryName)}</a>`
                 });
                 if (first) {
                     el.addClass('separator');
                     first = false;
                 }
-                categoryList.appendChild(el);
-            });
+                contextCategoryList.appendChild(el);
+            }
         },
 
         updateTagsSubMenu: function(tagList) {
@@ -491,15 +482,16 @@ window.qBittorrent.ContextMenu = (function() {
             }));
 
             const sortedTags = [];
-            for (const key in tagList)
-                sortedTags.push(tagList[key].name);
-            sortedTags.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
+            tagList.forEach((tag, hash) => sortedTags.push({
+                tagName: tag.name,
+                tagHash: hash
+            }));
+            sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName));
 
             for (let i = 0; i < sortedTags.length; ++i) {
-                const tagName = sortedTags[i];
-                const tagHash = genHash(tagName);
+                const { tagName, tagHash } = sortedTags[i];
                 const el = new Element('li', {
-                    html: '<a href="#Tag/' + tagHash + '" onclick="event.preventDefault(); torrentSetTagsFN(\'' + tagHash + '\', !event.currentTarget.getElement(\'input[type=checkbox]\').checked);">'
+                    html: `<a href="#Tag/${tagHash}" onclick="event.preventDefault(); torrentSetTagsFN(${tagHash}, !event.currentTarget.getElement('input[type=checkbox]').checked);">`
                         + '<input type="checkbox" onclick="this.checked = !this.checked;"> ' + window.qBittorrent.Misc.escapeHtml(tagName)
                         + '</a>'
                 });
@@ -513,8 +505,8 @@ window.qBittorrent.ContextMenu = (function() {
     const CategoriesFilterContextMenu = new Class({
         Extends: ContextMenu,
         updateMenuItems: function() {
-            const id = this.options.element.id;
-            if ((id != CATEGORIES_ALL) && (id != CATEGORIES_UNCATEGORIZED)) {
+            const id = Number(this.options.element.id);
+            if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
                 this.showItem('editCategory');
                 this.showItem('deleteCategory');
                 if (useSubcategories) {
@@ -535,8 +527,8 @@ window.qBittorrent.ContextMenu = (function() {
     const TagsFilterContextMenu = new Class({
         Extends: ContextMenu,
         updateMenuItems: function() {
-            const id = this.options.element.id;
-            if ((id !== TAGS_ALL.toString()) && (id !== TAGS_UNTAGGED.toString()))
+            const id = Number(this.options.element.id);
+            if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
                 this.showItem('deleteTag');
             else
                 this.hideItem('deleteTag');
diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js
index 485b867b0096..b3f03a21266e 100644
--- a/src/webui/www/private/scripts/dynamicTable.js
+++ b/src/webui/www/private/scripts/dynamicTable.js
@@ -1386,50 +1386,41 @@ window.qBittorrent.DynamicTable = (function() {
                     break;
             }
 
-            const categoryHashInt = parseInt(categoryHash);
-            if (!isNaN(categoryHashInt)) {
-                switch (categoryHashInt) {
-                    case CATEGORIES_ALL:
-                        break; // do nothing
-                    case CATEGORIES_UNCATEGORIZED:
-                        if (row['full_data'].category.length !== 0)
+            switch (categoryHash) {
+                case CATEGORIES_ALL:
+                    break; // do nothing
+                case CATEGORIES_UNCATEGORIZED:
+                    if (row['full_data'].category.length !== 0)
+                        return false;
+                    break; // do nothing
+                default:
+                    if (!useSubcategories) {
+                        if (categoryHash !== genHash(row['full_data'].category))
                             return false;
-                        break; // do nothing
-                    default:
-                        if (!useSubcategories) {
-                            if (categoryHashInt !== genHash(row['full_data'].category))
-                                return false;
-                        }
-                        else {
-                            const selectedCategoryName = category_list[categoryHash].name + "/";
-                            const torrentCategoryName = row['full_data'].category + "/";
-                            if (!torrentCategoryName.startsWith(selectedCategoryName))
-                                return false;
-                        }
-                }
+                    }
+                    else {
+                        const selectedCategoryName = category_list.get(categoryHash).name + "/";
+                        const torrentCategoryName = row['full_data'].category + "/";
+                        if (!torrentCategoryName.startsWith(selectedCategoryName))
+                            return false;
+                    }
+                    break;
             }
 
-            const tagHashInt = parseInt(tagHash);
-            const isNumber = !isNaN(tagHashInt);
-            if (isNumber) {
-                switch (tagHashInt) {
-                    case TAGS_ALL:
-                        break; // do nothing
+            switch (tagHash) {
+                case TAGS_ALL:
+                    break; // do nothing
 
-                    case TAGS_UNTAGGED:
-                        if (row['full_data'].tags.length !== 0)
-                            return false;
-                        break; // do nothing
+                case TAGS_UNTAGGED:
+                    if (row['full_data'].tags.length !== 0)
+                        return false;
+                    break; // do nothing
 
-                    default: {
-                        let rowTags = row['full_data'].tags.split(', ');
-                        rowTags = rowTags.map(function(tag) {
-                            return genHash(tag);
-                        });
-                        if (!rowTags.contains(tagHashInt))
-                            return false;
-                        break;
-                    }
+                default: {
+                    const tagHashes = row['full_data'].tags.split(', ').map(tag => genHash(tag));
+                    if (!tagHashes.contains(tagHash))
+                        return false;
+                    break;
                 }
             }
 
@@ -1460,9 +1451,10 @@ window.qBittorrent.DynamicTable = (function() {
             let cnt = 0;
             const rows = this.rows.getValues();
 
-            for (let i = 0; i < rows.length; ++i)
+            for (let i = 0; i < rows.length; ++i) {
                 if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, trackerHash, null))
                     ++cnt;
+            }
             return cnt;
         },
 
@@ -1470,9 +1462,10 @@ window.qBittorrent.DynamicTable = (function() {
             const rowsHashes = [];
             const rows = this.rows.getValues();
 
-            for (let i = 0; i < rows.length; ++i)
+            for (let i = 0; i < rows.length; ++i) {
                 if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, trackerHash, null))
                     rowsHashes.push(rows[i]['rowId']);
+            }
 
             return rowsHashes;
         },
diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js
index 3801bc78a89d..be3577b163b6 100644
--- a/src/webui/www/private/scripts/mocha-init.js
+++ b/src/webui/www/private/scripts/mocha-init.js
@@ -573,20 +573,21 @@ const initializeWindows = function() {
     };
 
     torrentSetCategoryFN = function(categoryHash) {
-        let categoryName = '';
-        if (categoryHash != 0)
-            categoryName = category_list[categoryHash].name;
         const hashes = torrentsTable.selectedRowsIds();
-        if (hashes.length) {
-            new Request({
-                url: 'api/v2/torrents/setCategory',
-                method: 'post',
-                data: {
-                    hashes: hashes.join("|"),
-                    category: categoryName
-                }
-            }).send();
-        }
+        if (hashes.length <= 0)
+            return;
+
+        const categoryName = category_list.has(categoryHash)
+            ? category_list.get(categoryHash).name
+            : '';
+        new Request({
+            url: 'api/v2/torrents/setCategory',
+            method: 'post',
+            data: {
+                hashes: hashes.join("|"),
+                category: categoryName
+            }
+        }).send();
     };
 
     createCategoryFN = function() {
@@ -609,7 +610,7 @@ const initializeWindows = function() {
 
     createSubcategoryFN = function(categoryHash) {
         const action = "createSubcategory";
-        const categoryName = category_list[categoryHash].name + "/";
+        const categoryName = category_list.get(categoryHash).name + "/";
         new MochaUI.Window({
             id: 'newSubcategoryPage',
             title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
@@ -628,13 +629,12 @@ const initializeWindows = function() {
 
     editCategoryFN = function(categoryHash) {
         const action = "edit";
-        const categoryName = category_list[categoryHash].name;
-        const savePath = category_list[categoryHash].savePath;
+        const category = category_list.get(categoryHash);
         new MochaUI.Window({
             id: 'editCategoryPage',
             title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
             loadMethod: 'iframe',
-            contentURL: new URI('newcategory.html').setData("action", action).setData("categoryName", categoryName).setData("savePath", savePath).toString(),
+            contentURL: new URI('newcategory.html').setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
             scrollbars: false,
             resizable: true,
             maximizable: false,
@@ -647,7 +647,7 @@ const initializeWindows = function() {
     };
 
     removeCategoryFN = function(categoryHash) {
-        const categoryName = category_list[categoryHash].name;
+        const categoryName = category_list.get(categoryHash).name;
         new Request({
             url: 'api/v2/torrents/removeCategories',
             method: 'post',
@@ -660,10 +660,11 @@ const initializeWindows = function() {
 
     deleteUnusedCategoriesFN = function() {
         const categories = [];
-        for (const hash in category_list) {
+        category_list.forEach((category, hash) => {
             if (torrentsTable.getFilteredTorrentsNumber('all', hash, TAGS_ALL, TRACKERS_ALL) === 0)
-                categories.push(category_list[hash].name);
-        }
+                categories.push(category.name);
+        });
+
         new Request({
             url: 'api/v2/torrents/removeCategories',
             method: 'post',
@@ -742,18 +743,19 @@ const initializeWindows = function() {
     };
 
     torrentSetTagsFN = function(tagHash, isSet) {
-        const tagName = ((tagHash === '0') ? '' : tagList[tagHash].name);
         const hashes = torrentsTable.selectedRowsIds();
-        if (hashes.length) {
-            new Request({
-                url: (isSet ? 'api/v2/torrents/addTags' : 'api/v2/torrents/removeTags'),
-                method: 'post',
-                data: {
-                    hashes: hashes.join("|"),
-                    tags: tagName,
-                }
-            }).send();
-        }
+        if (hashes.length <= 0)
+            return;
+
+        const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : '';
+        new Request({
+            url: (isSet ? 'api/v2/torrents/addTags' : 'api/v2/torrents/removeTags'),
+            method: 'post',
+            data: {
+                hashes: hashes.join("|"),
+                tags: tagName,
+            }
+        }).send();
     };
 
     torrentRemoveAllTagsFN = function() {
@@ -788,7 +790,7 @@ const initializeWindows = function() {
     };
 
     removeTagFN = function(tagHash) {
-        const tagName = tagList[tagHash].name;
+        const tagName = tagList.get(tagHash).name;
         new Request({
             url: 'api/v2/torrents/deleteTags',
             method: 'post',
@@ -801,10 +803,10 @@ const initializeWindows = function() {
 
     deleteUnusedTagsFN = function() {
         const tags = [];
-        for (const hash in tagList) {
+        tagList.forEach((tag, hash) => {
             if (torrentsTable.getFilteredTorrentsNumber('all', CATEGORIES_ALL, hash, TRACKERS_ALL) === 0)
-                tags.push(tagList[hash].name);
-        }
+                tags.push(tag.name);
+        });
         new Request({
             url: 'api/v2/torrents/deleteTags',
             method: 'post',
diff --git a/src/webui/www/private/views/filters.html b/src/webui/www/private/views/filters.html
index d509713be666..580815d8ece0 100644
--- a/src/webui/www/private/views/filters.html
+++ b/src/webui/www/private/views/filters.html
@@ -65,25 +65,25 @@
                     createCategoryFN();
                 },
                 createSubcategory: function(element, ref) {
-                    createSubcategoryFN(element.id);
+                    createSubcategoryFN(Number(element.id));
                 },
                 editCategory: function(element, ref) {
-                    editCategoryFN(element.id);
+                    editCategoryFN(Number(element.id));
                 },
                 deleteCategory: function(element, ref) {
-                    removeCategoryFN(element.id);
+                    removeCategoryFN(Number(element.id));
                 },
                 deleteUnusedCategories: function(element, ref) {
                     deleteUnusedCategoriesFN();
                 },
                 startTorrentsByCategory: function(element, ref) {
-                    startTorrentsByCategoryFN(element.id);
+                    startTorrentsByCategoryFN(Number(element.id));
                 },
                 pauseTorrentsByCategory: function(element, ref) {
-                    pauseTorrentsByCategoryFN(element.id);
+                    pauseTorrentsByCategoryFN(Number(element.id));
                 },
                 deleteTorrentsByCategory: function(element, ref) {
-                    deleteTorrentsByCategoryFN(element.id);
+                    deleteTorrentsByCategoryFN(Number(element.id));
                 }
             },
             offsets: {
@@ -103,19 +103,19 @@
                     createTagFN();
                 },
                 deleteTag: function(element, ref) {
-                    removeTagFN(element.id);
+                    removeTagFN(Number(element.id));
                 },
                 deleteUnusedTags: function(element, ref) {
                     deleteUnusedTagsFN();
                 },
                 startTorrentsByTag: function(element, ref) {
-                    startTorrentsByTagFN(element.id);
+                    startTorrentsByTagFN(Number(element.id));
                 },
                 pauseTorrentsByTag: function(element, ref) {
-                    pauseTorrentsByTagFN(element.id);
+                    pauseTorrentsByTagFN(Number(element.id));
                 },
                 deleteTorrentsByTag: function(element, ref) {
-                    deleteTorrentsByTagFN(element.id);
+                    deleteTorrentsByTagFN(Number(element.id));
                 }
             },
             offsets: {
diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html
index 387f4772c5c9..0b2e00f0cd0f 100644
--- a/src/webui/www/private/views/preferences.html
+++ b/src/webui/www/private/views/preferences.html
@@ -2043,7 +2043,11 @@
                         updateExportDirFinEnabled();
 
                         // Automatically add torrents from
-                        for (const [folder, folderType] of Object.entries(pref.scan_dirs)) {
+                        for (const folder in pref.scan_dirs) {
+                            if (!Object.hasOwn(pref.scan_dirs, folder))
+                                continue;
+
+                            const folderType = pref.scan_dirs[folder];
                             let sel = "";
                             let other = "";
                             if (typeof folderType === "number") {