diff --git a/app/assets/javascripts/admin/addon/components/admin-flag-item.gjs b/app/assets/javascripts/admin/addon/components/admin-flag-item.gjs
index d7fc69a5da4..075c6eba78c 100644
--- a/app/assets/javascripts/admin/addon/components/admin-flag-item.gjs
+++ b/app/assets/javascripts/admin/addon/components/admin-flag-item.gjs
@@ -88,6 +88,7 @@ export default class AdminFlagItem extends Component {
       this.dMenu.close();
     });
   }
+
   @action
   edit() {
     this.router.transitionTo("adminConfig.flags.edit", this.args.flag);
diff --git a/app/assets/javascripts/admin/addon/components/embeddable-host.js b/app/assets/javascripts/admin/addon/components/embeddable-host.js
index 467b928f826..8d4ae85cdfe 100644
--- a/app/assets/javascripts/admin/addon/components/embeddable-host.js
+++ b/app/assets/javascripts/admin/addon/components/embeddable-host.js
@@ -44,10 +44,12 @@ export default class EmbeddableHost extends Component.extend(
   edit() {
     this.set("editToggled", true);
   }
+
   @action
   onUserChange(user) {
     this.set("user", user);
   }
+
   @action
   save() {
     if (this.cantSave) {
diff --git a/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs b/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs
index 534c6ca63a7..9fd13ca761a 100644
--- a/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs
+++ b/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs
@@ -46,6 +46,11 @@ export default class InstallThemeModal extends Component {
   keyGenUrl = this.args.model.keyGenUrl || "/admin/themes/generate_key_pair";
   importUrl = this.args.model.importUrl || "/admin/themes/import";
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    this.args.model.clearParams?.();
+  }
+
   get showPublicKey() {
     return this.uploadUrl?.match?.(/^ssh:\/\/.+@.+$|.+@.+:.+$/);
   }
@@ -125,11 +130,6 @@ export default class InstallThemeModal extends Component {
     );
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-    this.args.model.clearParams?.();
-  }
-
   @action
   async generatePublicKey() {
     try {
diff --git a/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs b/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs
index 9ddf7d785cf..2d5ea461726 100644
--- a/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs
+++ b/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs
@@ -96,6 +96,7 @@ export default class FileTypesList extends Component {
 
     this.args.changeValueCallback(newTypes.join(TOKEN_SEPARATOR));
   }
+
   <template>
     <ListSetting
       @value={{this.settingValue}}
diff --git a/app/assets/javascripts/admin/addon/components/themes-list.js b/app/assets/javascripts/admin/addon/components/themes-list.js
index d35e6f38261..bcfed2ee1dd 100644
--- a/app/assets/javascripts/admin/addon/components/themes-list.js
+++ b/app/assets/javascripts/admin/addon/components/themes-list.js
@@ -166,6 +166,7 @@ export default class ThemesList extends Component {
     results = this._applyFilter(results);
     return this._searchThemes(results, this.searchTerm);
   }
+
   @discourseComputed("themesList.@each.markedToDelete")
   someInactiveSelected() {
     return (
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js
index acd33ca7b9e..e0edecd5818 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js
@@ -26,6 +26,7 @@ export default class AdminUserBadgesController extends Controller {
   availableBadges() {
     return grantableBadges(this.get("allBadges"), this.get("userBadges"));
   }
+
   @discourseComputed("model", "model.[]", "model.expandedBadges.[]")
   groupedBadges() {
     const allBadges = this.model;
@@ -71,6 +72,7 @@ export default class AdminUserBadgesController extends Controller {
 
     return expanded.sortBy("granted_at").reverse();
   }
+
   @action
   expandGroup(userBadge) {
     const model = this.model;
diff --git a/app/assets/javascripts/dialog-holder/addon/services/dialog.js b/app/assets/javascripts/dialog-holder/addon/services/dialog.js
index 4e8f7e4a170..5ea9429a848 100644
--- a/app/assets/javascripts/dialog-holder/addon/services/dialog.js
+++ b/app/assets/javascripts/dialog-holder/addon/services/dialog.js
@@ -27,6 +27,11 @@ export default class DialogService extends Service {
   class = null;
   _confirming = false;
 
+  willDestroy() {
+    this.dialogInstance?.destroy();
+    this.reset();
+  }
+
   async dialog(params) {
     const {
       message,
@@ -171,11 +176,6 @@ export default class DialogService extends Service {
     });
   }
 
-  willDestroy() {
-    this.dialogInstance?.destroy();
-    this.reset();
-  }
-
   @bind
   didConfirmWrapped() {
     if (this.didConfirm) {
diff --git a/app/assets/javascripts/discourse/app/components/basic-topic-list.js b/app/assets/javascripts/discourse/app/components/basic-topic-list.js
index 8c40b76a51d..e5bbf7c4361 100644
--- a/app/assets/javascripts/discourse/app/components/basic-topic-list.js
+++ b/app/assets/javascripts/discourse/app/components/basic-topic-list.js
@@ -9,6 +9,14 @@ export default class BasicTopicList extends Component {
 
   @not("loaded") loading;
 
+  init() {
+    super.init(...arguments);
+    const topicList = this.topicList;
+    if (topicList) {
+      this._initFromTopicList(topicList);
+    }
+  }
+
   @discourseComputed("topicList.loaded")
   loaded() {
     let topicList = this.topicList;
@@ -31,14 +39,6 @@ export default class BasicTopicList extends Component {
     }
   }
 
-  init() {
-    super.init(...arguments);
-    const topicList = this.topicList;
-    if (topicList) {
-      this._initFromTopicList(topicList);
-    }
-  }
-
   didInsertElement() {
     super.didInsertElement(...arguments);
 
diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js
index 6802f5bdbc8..9909cd22e49 100644
--- a/app/assets/javascripts/discourse/app/components/d-editor.js
+++ b/app/assets/javascripts/discourse/app/components/d-editor.js
@@ -76,6 +76,12 @@ export default class DEditor extends Component {
     },
   };
 
+  init() {
+    super.init(...arguments);
+
+    this.register = getRegister(this);
+  }
+
   @computed("formTemplateIds")
   get selectedFormTemplateId() {
     if (this._selectedFormTemplateId) {
@@ -116,12 +122,6 @@ export default class DEditor extends Component {
     }
   }
 
-  init() {
-    super.init(...arguments);
-
-    this.register = getRegister(this);
-  }
-
   didInsertElement() {
     super.didInsertElement(...arguments);
 
diff --git a/app/assets/javascripts/discourse/app/components/d-lightbox.js b/app/assets/javascripts/discourse/app/components/d-lightbox.js
index e8f57ff7089..b09d89fddca 100644
--- a/app/assets/javascripts/discourse/app/components/d-lightbox.js
+++ b/app/assets/javascripts/discourse/app/components/d-lightbox.js
@@ -48,6 +48,11 @@ export default class DLightbox extends Component {
   animationDuration = ANIMATION_DURATION;
   scrollPosition = 0;
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    this.cleanup();
+  }
+
   get layoutType() {
     return window.innerWidth > window.innerHeight
       ? LAYOUT_TYPES.HORIZONTAL
@@ -487,9 +492,4 @@ export default class DLightbox extends Component {
       });
     }
   }
-
-  willDestroy() {
-    super.willDestroy(...arguments);
-    this.cleanup();
-  }
 }
diff --git a/app/assets/javascripts/discourse/app/components/discourse-topic.js b/app/assets/javascripts/discourse/app/components/discourse-topic.js
index 15e753a29f7..77f927c2877 100644
--- a/app/assets/javascripts/discourse/app/components/discourse-topic.js
+++ b/app/assets/javascripts/discourse/app/components/discourse-topic.js
@@ -26,6 +26,20 @@ export default class DiscourseTopic extends Component.extend(Scrolling) {
   SHORT_POST = 1200;
   dockAt = 0;
 
+  init() {
+    super.init(...arguments);
+    this.appEvents.on("discourse:focus-changed", this, "gotFocus");
+    this.appEvents.on("post:highlight", this, "_highlightPost");
+  }
+
+  willDestroy() {
+    super.willDestroy(...arguments);
+
+    // this happens after route exit, stuff could have trickled in
+    this.appEvents.off("discourse:focus-changed", this, "gotFocus");
+    this.appEvents.off("post:highlight", this, "_highlightPost");
+  }
+
   @observes("enteredAt")
   _enteredTopic() {
     // Ember is supposed to only call observers when values change but something
@@ -43,12 +57,6 @@ export default class DiscourseTopic extends Component.extend(Scrolling) {
     }
   }
 
-  init() {
-    super.init(...arguments);
-    this.appEvents.on("discourse:focus-changed", this, "gotFocus");
-    this.appEvents.on("post:highlight", this, "_highlightPost");
-  }
-
   didInsertElement() {
     super.didInsertElement(...arguments);
 
@@ -61,14 +69,6 @@ export default class DiscourseTopic extends Component.extend(Scrolling) {
     );
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-
-    // this happens after route exit, stuff could have trickled in
-    this.appEvents.off("discourse:focus-changed", this, "gotFocus");
-    this.appEvents.off("post:highlight", this, "_highlightPost");
-  }
-
   willDestroyElement() {
     super.willDestroyElement(...arguments);
 
diff --git a/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs b/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs
index d6b5fc5be6c..d4c5cb26586 100644
--- a/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs
+++ b/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs
@@ -49,6 +49,25 @@ export default class GlimmerSiteHeader extends Component {
     schedule("afterRender", () => this.animateMenu());
   }
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    this.appEvents.off("user-menu:rendered", this, this.animateMenu);
+
+    if (this.dropDownHeaderEnabled) {
+      this.appEvents.off(
+        "sidebar-hamburger-dropdown:rendered",
+        this,
+        this.animateMenu
+      );
+    }
+
+    this._itsatrap?.destroy();
+    this._itsatrap = null;
+
+    window.removeEventListener("scroll", this._recalculateHeaderOffset);
+    this._resizeObserver?.disconnect();
+  }
+
   get dropDownHeaderEnabled() {
     return !this.sidebarEnabled || this.site.narrowDesktopView;
   }
@@ -361,25 +380,6 @@ export default class GlimmerSiteHeader extends Component {
     );
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-    this.appEvents.off("user-menu:rendered", this, this.animateMenu);
-
-    if (this.dropDownHeaderEnabled) {
-      this.appEvents.off(
-        "sidebar-hamburger-dropdown:rendered",
-        this,
-        this.animateMenu
-      );
-    }
-
-    this._itsatrap?.destroy();
-    this._itsatrap = null;
-
-    window.removeEventListener("scroll", this._recalculateHeaderOffset);
-    this._resizeObserver?.disconnect();
-  }
-
   <template>
     <div
       class={{concatClass
diff --git a/app/assets/javascripts/discourse/app/components/modal/bookmark.js b/app/assets/javascripts/discourse/app/components/modal/bookmark.js
index 0abbf266104..4310383566a 100644
--- a/app/assets/javascripts/discourse/app/components/modal/bookmark.js
+++ b/app/assets/javascripts/discourse/app/components/modal/bookmark.js
@@ -53,6 +53,13 @@ export default class BookmarkModal extends Component {
 
   _itsatrap = new ItsATrap();
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    this._itsatrap?.destroy();
+    this._itsatrap = null;
+    KeyboardShortcuts.unpause();
+  }
+
   get bookmark() {
     return this.args.model.bookmark;
   }
@@ -127,13 +134,6 @@ export default class BookmarkModal extends Component {
     return labels;
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-    this._itsatrap?.destroy();
-    this._itsatrap = null;
-    KeyboardShortcuts.unpause();
-  }
-
   @action
   didInsert() {
     discourseLater(() => {
diff --git a/app/assets/javascripts/discourse/app/components/modal/change-owner.js b/app/assets/javascripts/discourse/app/components/modal/change-owner.js
index 62ca41d60bf..a2c908dea67 100644
--- a/app/assets/javascripts/discourse/app/components/modal/change-owner.js
+++ b/app/assets/javascripts/discourse/app/components/modal/change-owner.js
@@ -26,6 +26,7 @@ export default class ChangeOwnerModal extends Component {
   get selectedPostsCount() {
     return this.args.model.selectedPostsCount;
   }
+
   @action
   async changeOwnershipOfPosts() {
     this.saving = true;
diff --git a/app/assets/javascripts/discourse/app/components/plugin-outlet.js b/app/assets/javascripts/discourse/app/components/plugin-outlet.js
index aa50a47a5b1..42e65f51af9 100644
--- a/app/assets/javascripts/discourse/app/components/plugin-outlet.js
+++ b/app/assets/javascripts/discourse/app/components/plugin-outlet.js
@@ -141,9 +141,11 @@ export default class PluginOutletComponent extends GlimmerComponentWithDeprecate
     });
     return this.#parentView;
   }
+
   set parentView(value) {
     this.#parentView = value;
   }
+
   get _parentView() {
     return this.parentView;
   }
@@ -166,9 +168,11 @@ class PluginOutletWithTagNameWrapper extends ClassicComponent {
       return this.#parentView.parentView;
     }
   }
+
   set parentView(value) {
     this.#parentView = value;
   }
+
   get _parentView() {
     return this.parentView;
   }
diff --git a/app/assets/javascripts/discourse/app/components/search-menu.js b/app/assets/javascripts/discourse/app/components/search-menu.js
index aa4ed7386fc..a15aec68804 100644
--- a/app/assets/javascripts/discourse/app/components/search-menu.js
+++ b/app/assets/javascripts/discourse/app/components/search-menu.js
@@ -47,6 +47,14 @@ export default class SearchMenu extends Component {
   _debouncer = null;
   _activeSearch = null;
 
+  willDestroy() {
+    if (!this.args.inlineResults) {
+      document.removeEventListener("mousedown", this.onDocumentPress);
+      document.removeEventListener("touchend", this.onDocumentPress);
+    }
+    super.willDestroy(...arguments);
+  }
+
   @bind
   setupEventListeners() {
     // We only need to register click events when the search menu is rendered outside of the header.
@@ -57,14 +65,6 @@ export default class SearchMenu extends Component {
     }
   }
 
-  willDestroy() {
-    if (!this.args.inlineResults) {
-      document.removeEventListener("mousedown", this.onDocumentPress);
-      document.removeEventListener("touchend", this.onDocumentPress);
-    }
-    super.willDestroy(...arguments);
-  }
-
   @bind
   onDocumentPress(event) {
     if (!this.menuPanelOpen) {
diff --git a/app/assets/javascripts/discourse/app/components/sidebar.js b/app/assets/javascripts/discourse/app/components/sidebar.js
index e11fd067f1c..72adad415e0 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar.js
@@ -17,6 +17,13 @@ export default class Sidebar extends Component {
     }
   }
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    if (this.site.mobileView) {
+      document.removeEventListener("click", this.collapseSidebar);
+    }
+  }
+
   get showSwitchPanelButtonsOnTop() {
     return this.siteSettings.default_sidebar_switch_panel_position === "top";
   }
@@ -55,11 +62,4 @@ export default class Sidebar extends Component {
       this.args.toggleSidebar();
     }
   }
-
-  willDestroy() {
-    super.willDestroy(...arguments);
-    if (this.site.mobileView) {
-      document.removeEventListener("click", this.collapseSidebar);
-    }
-  }
 }
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.gjs b/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.gjs
index f258a0ab318..4de63801d0f 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.gjs
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.gjs
@@ -44,6 +44,7 @@ export default class SectionFormLink extends Component {
       }
     }
   }
+
   @action
   dragEnter() {
     this.dragCount++;
diff --git a/app/assets/javascripts/discourse/app/components/topic-map/private-message-map.gjs b/app/assets/javascripts/discourse/app/components/topic-map/private-message-map.gjs
index 1564c8b4c8e..210ea5925d2 100644
--- a/app/assets/javascripts/discourse/app/components/topic-map/private-message-map.gjs
+++ b/app/assets/javascripts/discourse/app/components/topic-map/private-message-map.gjs
@@ -205,6 +205,7 @@ class PmRemoveLink extends Component {
       didConfirm: () => this.args.removeAllowedUser(this.args.model),
     });
   }
+
   <template>
     <DButton
       class="remove-invited"
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/tracking.js b/app/assets/javascripts/discourse/app/controllers/preferences/tracking.js
index 76f261094da..acc176f58e2 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/tracking.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/tracking.js
@@ -101,6 +101,7 @@ export default class extends Controller {
       )
       .filter((t) => t);
   }
+
   @computed(
     "model.watchedCategories",
     "model.mutedCategories",
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/users.js b/app/assets/javascripts/discourse/app/controllers/preferences/users.js
index 7a70d328128..03f1353a78c 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/users.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/users.js
@@ -12,6 +12,17 @@ export default class UsersController extends Controller {
   )
   allowPmUsersEnabled;
 
+  init() {
+    super.init(...arguments);
+
+    this.saveAttrNames = [
+      "allow_private_messages",
+      "muted_usernames",
+      "allowed_pm_usernames",
+      "enable_allowed_pm_users",
+    ];
+  }
+
   @computed("model.muted_usernames")
   get mutedUsernames() {
     let usernames = this.model.muted_usernames;
@@ -34,17 +45,6 @@ export default class UsersController extends Controller {
     return makeArray(usernames).uniq();
   }
 
-  init() {
-    super.init(...arguments);
-
-    this.saveAttrNames = [
-      "allow_private_messages",
-      "muted_usernames",
-      "allowed_pm_usernames",
-      "enable_allowed_pm_users",
-    ];
-  }
-
   @action
   onChangeMutedUsernames(usernames) {
     this.model.set("muted_usernames", usernames.uniq().join(","));
diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.gjs b/app/assets/javascripts/discourse/app/lib/plugin-api.gjs
index 2c3066b9433..b4d1afa2ca5 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.gjs
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.gjs
@@ -2273,6 +2273,7 @@ class PluginApi {
   addSaveableUserField(fieldName) {
     addSaveableUserField(fieldName);
   }
+
   addSaveableUserOptionField(fieldName) {
     addSaveableUserOptionField(fieldName);
   }
diff --git a/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js b/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js
index 803ad35c0de..63af0a7cb4a 100644
--- a/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js
+++ b/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js
@@ -7,6 +7,7 @@ export default class UserMenuBaseItem {
     this.site = site;
     this.siteSettings = siteSettings;
   }
+
   get className() {}
 
   get linkHref() {
diff --git a/app/assets/javascripts/discourse/app/models/site.js b/app/assets/javascripts/discourse/app/models/site.js
index c5628d57d4d..ad11d0ffd31 100644
--- a/app/assets/javascripts/discourse/app/models/site.js
+++ b/app/assets/javascripts/discourse/app/models/site.js
@@ -81,13 +81,6 @@ export default class Site extends RestModel.extend().reopenClass(Singleton) {
 
   @sort("categories", "topicCountDesc") categoriesByCount;
 
-  @computed("categories.[]")
-  get categoriesById() {
-    const map = new Map();
-    this.categories.forEach((c) => map.set(c.id, c));
-    return map;
-  }
-
   init() {
     super.init(...arguments);
 
@@ -95,6 +88,13 @@ export default class Site extends RestModel.extend().reopenClass(Singleton) {
     this.categories = this.categories || [];
   }
 
+  @computed("categories.[]")
+  get categoriesById() {
+    const map = new Map();
+    this.categories.forEach((c) => map.set(c.id, c));
+    return map;
+  }
+
   @discourseComputed("notification_types")
   notificationLookup(notificationTypes) {
     const result = [];
diff --git a/app/assets/javascripts/discourse/app/services/client-error-handler.js b/app/assets/javascripts/discourse/app/services/client-error-handler.js
index 51f5307ef2c..62ec75eddc9 100644
--- a/app/assets/javascripts/discourse/app/services/client-error-handler.js
+++ b/app/assets/javascripts/discourse/app/services/client-error-handler.js
@@ -26,10 +26,6 @@ export default class ClientErrorHandlerService extends Service {
     document.addEventListener("discourse-error", this.handleDiscourseError);
   }
 
-  get rootElement() {
-    return document.querySelector(getOwner(this).rootElement);
-  }
-
   willDestroy() {
     document.removeEventListener("discourse-error", this.handleDiscourseError);
     this.rootElement
@@ -37,6 +33,10 @@ export default class ClientErrorHandlerService extends Service {
       .forEach((e) => e.remove());
   }
 
+  get rootElement() {
+    return document.querySelector(getOwner(this).rootElement);
+  }
+
   @bind
   handleDiscourseError(e) {
     if (e.detail?.themeId) {
diff --git a/app/assets/javascripts/discourse/app/services/lightbox.js b/app/assets/javascripts/discourse/app/services/lightbox.js
index b398a065af6..8337a02b913 100644
--- a/app/assets/javascripts/discourse/app/services/lightbox.js
+++ b/app/assets/javascripts/discourse/app/services/lightbox.js
@@ -60,6 +60,10 @@ export default class LightboxService extends Service {
     );
   }
 
+  willDestroy() {
+    this.#reset();
+  }
+
   @bind
   async onLightboxOpened({ items, currentItem }) {
     this.originalSiteThemeColor = await getSiteThemeColor();
@@ -268,8 +272,4 @@ export default class LightboxService extends Service {
 
     event.target.toggleAttribute(SELECTORS.DOCUMENT_LAST_FOCUSED_ELEMENT);
   }
-
-  willDestroy() {
-    this.#reset();
-  }
 }
diff --git a/app/assets/javascripts/discourse/app/services/notifications.js b/app/assets/javascripts/discourse/app/services/notifications.js
index edd614c8da8..a924fb4136d 100644
--- a/app/assets/javascripts/discourse/app/services/notifications.js
+++ b/app/assets/javascripts/discourse/app/services/notifications.js
@@ -16,6 +16,10 @@ export default class NotificationsService extends Service {
     this._checkDoNotDisturb();
   }
 
+  willDestroy() {
+    clearTimeout(this.#dndTimer);
+  }
+
   _checkDoNotDisturb() {
     clearTimeout(this.#dndTimer);
 
@@ -38,8 +42,4 @@ export default class NotificationsService extends Service {
       this.isInDoNotDisturb = false;
     }
   }
-
-  willDestroy() {
-    clearTimeout(this.#dndTimer);
-  }
 }
diff --git a/app/assets/javascripts/discourse/app/services/presence.js b/app/assets/javascripts/discourse/app/services/presence.js
index d1dec361942..7968c4167c5 100644
--- a/app/assets/javascripts/discourse/app/services/presence.js
+++ b/app/assets/javascripts/discourse/app/services/presence.js
@@ -277,10 +277,6 @@ export default class PresenceService extends Service {
     }
   }
 
-  get _presentChannels() {
-    return new Set(this._presentProxies.keys());
-  }
-
   willDestroy() {
     super.willDestroy(...arguments);
     window.removeEventListener("beforeunload", this._beaconLeaveAll);
@@ -288,6 +284,10 @@ export default class PresenceService extends Service {
     cancel(this._debounceTimer);
   }
 
+  get _presentChannels() {
+    return new Set(this._presentProxies.keys());
+  }
+
   // Get a PresenceChannel object representing a single channel
   getChannel(channelName) {
     return PresenceChannel.create({
diff --git a/app/assets/javascripts/discourse/app/services/route-scroll-manager.js b/app/assets/javascripts/discourse/app/services/route-scroll-manager.js
index e20233e9a15..79a6dfda8a3 100644
--- a/app/assets/javascripts/discourse/app/services/route-scroll-manager.js
+++ b/app/assets/javascripts/discourse/app/services/route-scroll-manager.js
@@ -24,6 +24,17 @@ export default class RouteScrollManager extends Service {
     ? document.getElementById("ember-testing-container")
     : document.scrollingElement;
 
+  init() {
+    super.init(...arguments);
+    this.router.on("routeDidChange", this.routeDidChange);
+    this.router.on("routeWillChange", this.routeWillChange);
+  }
+
+  willDestroy() {
+    this.router.off("routeDidChange", this.routeDidChange);
+    this.router.off("routeWillChange", this.routeWillChange);
+  }
+
   @bind
   routeWillChange() {
     this.historyStore.set(STORE_KEY, [
@@ -63,15 +74,4 @@ export default class RouteScrollManager extends Service {
     // No overrides - default to true
     return true;
   }
-
-  init() {
-    super.init(...arguments);
-    this.router.on("routeDidChange", this.routeDidChange);
-    this.router.on("routeWillChange", this.routeWillChange);
-  }
-
-  willDestroy() {
-    this.router.off("routeDidChange", this.routeDidChange);
-    this.router.off("routeWillChange", this.routeWillChange);
-  }
 }
diff --git a/app/assets/javascripts/discourse/app/widgets/post-cooked.js b/app/assets/javascripts/discourse/app/widgets/post-cooked.js
index e4cdb7d0a70..5a03038bc5d 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-cooked.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-cooked.js
@@ -52,15 +52,6 @@ export default class PostCooked {
       : null;
   }
 
-  update(prev) {
-    if (
-      prev.attrs.cooked !== this.attrs.cooked ||
-      prev.attrs.highlightTerm !== this.attrs.highlightTerm
-    ) {
-      return this.init();
-    }
-  }
-
   init() {
     this.originalQuoteContents = null;
     // todo should be a better way of detecting if it is composer preview
@@ -77,6 +68,15 @@ export default class PostCooked {
     return this.cookedDiv;
   }
 
+  update(prev) {
+    if (
+      prev.attrs.cooked !== this.attrs.cooked ||
+      prev.attrs.highlightTerm !== this.attrs.highlightTerm
+    ) {
+      return this.init();
+    }
+  }
+
   destroy() {
     this._stopTrackingMentionedUsersStatus();
     destroyUserStatusOnMentions();
diff --git a/app/assets/javascripts/discourse/app/widgets/widget.js b/app/assets/javascripts/discourse/app/widgets/widget.js
index 941891d9c92..f595717c2d5 100644
--- a/app/assets/javascripts/discourse/app/widgets/widget.js
+++ b/app/assets/javascripts/discourse/app/widgets/widget.js
@@ -183,6 +183,7 @@ export default class Widget {
     }
   }
 
+  init() {}
   transform() {
     return {};
   }
@@ -191,8 +192,6 @@ export default class Widget {
     return {};
   }
 
-  init() {}
-
   destroy() {}
 
   get(propertyPath) {
diff --git a/app/assets/javascripts/discourse/lib/with-side-watch.js b/app/assets/javascripts/discourse/lib/with-side-watch.js
index 459549ba247..d11a5959886 100644
--- a/app/assets/javascripts/discourse/lib/with-side-watch.js
+++ b/app/assets/javascripts/discourse/lib/with-side-watch.js
@@ -8,6 +8,7 @@ class BroccoliNoOp extends Plugin {
   constructor(path) {
     super([new WatchedDir(path)]);
   }
+
   build() {}
 }
 
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js
index a9818abd6f7..9a9252010f5 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js
@@ -50,8 +50,6 @@ acceptance("Sidebar - Plugin API", function (needs) {
               },
             ];
 
-            willDestroy = () => (sectionDestroy = "section test");
-
             links = [
               new (class extends BaseCustomSidebarSectionLink {
                 name = "random-channel";
@@ -99,6 +97,7 @@ acceptance("Sidebar - Plugin API", function (needs) {
                 text = "Homepage";
               })(),
             ];
+            willDestroy = () => (sectionDestroy = "section test");
           };
         }
       );
diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-post-decorate-cooked-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-post-decorate-cooked-test.js
index 86acd205e70..66eebe53dfa 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/topic-post-decorate-cooked-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/topic-post-decorate-cooked-test.js
@@ -14,6 +14,7 @@ acceptance("Acceptance | decorateCookedElement", function () {
         DemoComponent.eventLog.push("created");
         super(...arguments);
       }
+
       willDestroy() {
         super.willDestroy(...arguments);
         DemoComponent.eventLog.push("willDestroy");
diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js
index d8f56926ff7..9f8c56e6a57 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/render-glimmer-test.js
@@ -47,6 +47,7 @@ class DemoWidget extends Widget {
       ),
     ];
   }
+
   dummyAction() {}
 
   @bind
@@ -67,6 +68,11 @@ class DemoComponent extends ClassicComponent {
     super.init(...arguments);
   }
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    DemoComponent.eventLog.push("willDestroy");
+  }
+
   didInsertElement() {
     super.didInsertElement(...arguments);
     DemoComponent.eventLog.push("didInsertElement");
@@ -81,11 +87,6 @@ class DemoComponent extends ClassicComponent {
     super.didReceiveAttrs(...arguments);
     DemoComponent.eventLog.push("didReceiveAttrs");
   }
-
-  willDestroy() {
-    super.willDestroy(...arguments);
-    DemoComponent.eventLog.push("willDestroy");
-  }
 }
 
 class ToggleDemoWidget extends Widget {
diff --git a/app/assets/javascripts/discourse/tests/unit/lib/plugin-api-test.js b/app/assets/javascripts/discourse/tests/unit/lib/plugin-api-test.js
index 4240487ca0a..f18c408cad5 100644
--- a/app/assets/javascripts/discourse/tests/unit/lib/plugin-api-test.js
+++ b/app/assets/javascripts/discourse/tests/unit/lib/plugin-api-test.js
@@ -123,9 +123,11 @@ module("Unit | Utility | plugin-api", function (hooks) {
       static someStaticMethod() {
         return "original static method";
       }
+
       someFunction() {
         return "original function";
       }
+
       get someGetter() {
         return "original getter";
       }
diff --git a/app/assets/javascripts/select-kit/addon/components/category-row.gjs b/app/assets/javascripts/select-kit/addon/components/category-row.gjs
index be6c6e3ef4d..f62e34b37b9 100644
--- a/app/assets/javascripts/select-kit/addon/components/category-row.gjs
+++ b/app/assets/javascripts/select-kit/addon/components/category-row.gjs
@@ -253,6 +253,7 @@ export default class CategoryRow extends Component {
       description.length > limit ? "&hellip;" : ""
     }`;
   }
+
   _isValidInput(eventKey) {
     // relying on passing the event to the input is risky as it could not work
     // dispatching the event won't work as the event won't be trusted
diff --git a/app/assets/javascripts/select-kit/addon/components/tag-chooser.js b/app/assets/javascripts/select-kit/addon/components/tag-chooser.js
index 5395e059f34..07ef028ec6f 100644
--- a/app/assets/javascripts/select-kit/addon/components/tag-chooser.js
+++ b/app/assets/javascripts/select-kit/addon/components/tag-chooser.js
@@ -23,6 +23,16 @@ export default class TagChooser extends MultiSelectComponent.extend(TagsMixin) {
   excludeSynonyms = false;
   excludeHasSynonyms = false;
 
+  init() {
+    super.init(...arguments);
+
+    this.setProperties({
+      blockedTags: this.blockedTags || [],
+      termMatchesForbidden: false,
+      termMatchErrorMessage: null,
+    });
+  }
+
   modifyComponentForRow(collection, item) {
     if (this.getValue(item) === this.selectKit.filter && !item.count) {
       return "select-kit/select-kit-row";
@@ -50,16 +60,6 @@ export default class TagChooser extends MultiSelectComponent.extend(TagsMixin) {
     return null;
   }
 
-  init() {
-    super.init(...arguments);
-
-    this.setProperties({
-      blockedTags: this.blockedTags || [],
-      termMatchesForbidden: false,
-      termMatchErrorMessage: null,
-    });
-  }
-
   @computed("tags.[]")
   get value() {
     return makeArray(this.tags).uniq();
diff --git a/app/assets/javascripts/select-kit/addon/components/tag-drop.js b/app/assets/javascripts/select-kit/addon/components/tag-drop.js
index 52519efb95e..aa604bdda02 100644
--- a/app/assets/javascripts/select-kit/addon/components/tag-drop.js
+++ b/app/assets/javascripts/select-kit/addon/components/tag-drop.js
@@ -40,6 +40,12 @@ export default class TagDrop extends ComboBoxComponent.extend(TagsMixin) {
 
   @readOnly("tagId") value;
 
+  init() {
+    super.init(...arguments);
+
+    this.insertAfterCollection(MAIN_COLLECTION, MORE_TAGS_COLLECTION);
+  }
+
   @computed("maxTagsInFilterList", "topTags.[]", "mainCollection.[]")
   get shouldShowMoreTags() {
     if (this.selectKit.filter?.length > 0) {
@@ -49,12 +55,6 @@ export default class TagDrop extends ComboBoxComponent.extend(TagsMixin) {
     }
   }
 
-  init() {
-    super.init(...arguments);
-
-    this.insertAfterCollection(MAIN_COLLECTION, MORE_TAGS_COLLECTION);
-  }
-
   modifyComponentForCollection(collection) {
     if (collection === MORE_TAGS_COLLECTION) {
       return FilterForMore;
diff --git a/package.json b/package.json
index c816e553416..f8d57e49330 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
   "license": "GPL-2.0-only",
   "devDependencies": {
     "@babel/plugin-proposal-decorators": "^7.25.9",
-    "@discourse/lint-configs": "^2.1.0",
+    "@discourse/lint-configs": "^2.2.0",
     "@discourse/moment-timezone-names-translations": "^1.0.0",
     "@fortawesome/fontawesome-free": "6.6.0",
     "@glint/core": "^1.5.0",
@@ -45,8 +45,8 @@
     "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
     "lint:js": "eslint ./app/assets/javascripts $(script/list_bundled_plugins) --cache --no-error-on-unmatched-pattern",
     "lint:js:fix": "eslint --fix ./app/assets/javascripts $(script/list_bundled_plugins) --no-error-on-unmatched-pattern",
-    "lint:hbs": "ember-template-lint 'app/assets/javascripts/**/*.{gjs,hbs}' 'plugins/**/assets/javascripts/**/*.{gjs,hbs}' --no-error-on-unmatched-pattern",
-    "lint:hbs:fix": "ember-template-lint 'app/assets/javascripts/**/*.{gjs,hbs}' 'plugins/**/assets/javascripts/**/*.{gjs,hbs}' --no-error-on-unmatched-pattern --fix",
+    "lint:hbs": "ember-template-lint 'app/assets/javascripts/**/*.{gjs,hbs}' $(script/list_bundled_plugins '/assets/javascripts/**/*.{gjs,hbs}') --no-error-on-unmatched-pattern",
+    "lint:hbs:fix": "ember-template-lint 'app/assets/javascripts/**/*.{gjs,hbs}' $(script/list_bundled_plugins '/assets/javascripts/**/*.{gjs,hbs}')  --no-error-on-unmatched-pattern --fix",
     "lint:prettier": "pnpm pprettier --list-different 'app/assets/stylesheets/**/*.scss' 'app/assets/javascripts/**/*.{js,gjs,hbs}' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') $(script/list_bundled_plugins '/assets/javascripts/**/*.{js,gjs,hbs}')",
     "lint:prettier:fix": "pnpm prettier -w 'app/assets/stylesheets/**/*.scss' 'app/assets/javascripts/**/*.{js,gjs,hbs}' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') $(script/list_bundled_plugins '/assets/javascripts/**/*.{js,gjs,hbs}')",
     "lttf:ignore": "lint-to-the-future ignore",
diff --git a/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-edit.hbs b/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-edit.hbs
index d43749415d6..8a784c63c56 100644
--- a/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-edit.hbs
+++ b/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-edit.hbs
@@ -106,7 +106,7 @@
           </div>
         {{/if}}
 
-        {{#each triggerFields as |field|}}
+        {{#each this.triggerFields as |field|}}
           <AutomationField
             @automation={{this.automation}}
             @field={{field}}
diff --git a/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-index.hbs b/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-index.hbs
index 96c4e5cf2a2..294913e6b65 100644
--- a/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-index.hbs
+++ b/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation-index.hbs
@@ -1,4 +1,4 @@
-{{#if model.length}}
+{{#if this.model.length}}
   <table class="automations">
     <thead>
       <tr>
@@ -13,7 +13,7 @@
       </tr>
     </thead>
     <tbody>
-      {{#each model as |automation|}}
+      {{#each this.model as |automation|}}
         <tr>
           {{#if automation.script.not_found}}
             <td colspan="5" class="alert alert-danger">
diff --git a/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation.hbs b/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation.hbs
index 516c8183c9c..12e31b5ca26 100644
--- a/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation.hbs
+++ b/plugins/automation/admin/assets/javascripts/admin/templates/admin-plugins-discourse-automation.hbs
@@ -38,7 +38,7 @@
     {{i18n "discourse_automation.title"}}
   </h1>
 
-  {{#if showNewAutomation}}
+  {{#if this.showNewAutomation}}
     <DButton
       @label="discourse_automation.create"
       @icon="plus"
diff --git a/plugins/automation/admin/assets/javascripts/admin/templates/components/topic-trigger.hbs b/plugins/automation/admin/assets/javascripts/admin/templates/components/topic-trigger.hbs
index 3c893a1284d..bf89f192936 100644
--- a/plugins/automation/admin/assets/javascripts/admin/templates/components/topic-trigger.hbs
+++ b/plugins/automation/admin/assets/javascripts/admin/templates/components/topic-trigger.hbs
@@ -5,8 +5,8 @@
 
   <div class="controls">
     <Input
-      @value={{metadata.topic_id}}
-      {{on "input" (action (mut metadata.topic_id) value="target.value")}}
+      @value={{this.metadata.topic_id}}
+      {{on "input" (action (mut this.metadata.topic_id) value="target.value")}}
     />
   </div>
 </div>
\ No newline at end of file
diff --git a/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/new.hbs b/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/new.hbs
index 2ed27eeb6c6..5fc3a1706b4 100644
--- a/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/new.hbs
+++ b/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/new.hbs
@@ -5,5 +5,5 @@
     class="incoming-chat-webhooks-back"
   />
 
-  <ChatIncomingWebhookEditForm @chatChannels={{model.chat_channels}} />
+  <ChatIncomingWebhookEditForm @chatChannels={{this.model.chat_channels}} />
 </div>
\ No newline at end of file
diff --git a/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/show.hbs b/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/show.hbs
index b4b1220ba1e..4a47bdc1345 100644
--- a/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/show.hbs
+++ b/plugins/chat/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-incoming-webhooks/show.hbs
@@ -5,10 +5,10 @@
     class="incoming-chat-webhooks-back"
   />
 
-  <ConditionalLoadingSpinner @condition={{not model.webhook}}>
+  <ConditionalLoadingSpinner @condition={{not this.model.webhook}}>
     <ChatIncomingWebhookEditForm
-      @webhook={{model.webhook}}
-      @chatChannels={{model.chat_channels}}
+      @webhook={{this.model.webhook}}
+      @chatChannels={{this.model.chat_channels}}
     />
   </ConditionalLoadingSpinner>
 </div>
\ No newline at end of file
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs
index 7a8316176f9..0d83337d0a0 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs
@@ -24,6 +24,12 @@ export default class ChatChannelMessageEmojiPicker extends Component {
     };
   });
 
+  @action
+  willDestroy() {
+    super.willDestroy(...arguments);
+    this._popper?.destroy();
+  }
+
   @action
   didSelectEmoji(emoji) {
     this.chatEmojiPickerManager.picker?.didSelectEmoji(emoji);
@@ -58,12 +64,6 @@ export default class ChatChannelMessageEmojiPicker extends Component {
     element.classList.remove("hidden");
   }
 
-  @action
-  willDestroy() {
-    super.willDestroy(...arguments);
-    this._popper?.destroy();
-  }
-
   <template>
     <ChatEmojiPicker
       {{this.listenToBodyScroll}}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-footer.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-footer.gjs
index e20f64f34ef..8111199502e 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-footer.gjs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-footer.gjs
@@ -43,6 +43,7 @@ export default class ChatFooter extends Component {
       this.siteSettings.enable_public_channels,
     ].filter(Boolean).length;
   }
+
   get shouldRenderFooter() {
     return (
       (this.site.mobileView || this.chatStateManager.isDrawerExpanded) &&
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-header.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-header.gjs
index 1d661a10ec6..002035b4c05 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-header.gjs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-header.gjs
@@ -21,17 +21,17 @@ export default class ChatHeader extends Component {
     this.router.on("routeDidChange", this, this.#updatePreviousURL);
   }
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+    this.router.off("routeDidChange", this, this.#updatePreviousURL);
+  }
+
   get shouldRender() {
     return (
       this.siteSettings.chat_enabled && this.site.mobileView && this.isChatOpen
     );
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-    this.router.off("routeDidChange", this, this.#updatePreviousURL);
-  }
-
   get isChatOpen() {
     return this.router.currentURL.startsWith("/chat");
   }
diff --git a/plugins/chat/assets/javascripts/discourse/components/user-threads/index.gjs b/plugins/chat/assets/javascripts/discourse/components/user-threads/index.gjs
index e16d517287f..bbca6f82d4c 100644
--- a/plugins/chat/assets/javascripts/discourse/components/user-threads/index.gjs
+++ b/plugins/chat/assets/javascripts/discourse/components/user-threads/index.gjs
@@ -21,11 +21,6 @@ export default class UserThreads extends Component {
 
   trackedChannels = {};
 
-  @cached
-  get threadsCollection() {
-    return this.chatApi.userThreads(this.handleLoadedThreads);
-  }
-
   willDestroy() {
     super.willDestroy(...arguments);
 
@@ -36,6 +31,11 @@ export default class UserThreads extends Component {
     this.trackedChannels = {};
   }
 
+  @cached
+  get threadsCollection() {
+    return this.chatApi.userThreads(this.handleLoadedThreads);
+  }
+
   @bind
   handleLoadedThreads(result) {
     return result.threads.map((threadObject) => {
diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat.js b/plugins/chat/assets/javascripts/discourse/controllers/chat.js
index 8ba83db0e44..c9a733038b6 100644
--- a/plugins/chat/assets/javascripts/discourse/controllers/chat.js
+++ b/plugins/chat/assets/javascripts/discourse/controllers/chat.js
@@ -27,6 +27,7 @@ export default class ChatController extends Controller {
       this.chatStateManager.hasPreloadedChannels
     );
   }
+
   get shouldUseCoreSidebar() {
     return this.siteSettings.navigation_menu === "sidebar";
   }
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-drafts-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-drafts-manager.js
index 4e2f6c2f4fd..b7b7efcdfd6 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-drafts-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-drafts-manager.js
@@ -6,6 +6,10 @@ export default class ChatDraftsManager extends Service {
 
   drafts = {};
 
+  willDestroy() {
+    cancel(this?._persistHandler);
+  }
+
   async add(message, channelId, threadId) {
     try {
       this.drafts[this.key(channelId, threadId)] = message;
@@ -46,8 +50,4 @@ export default class ChatDraftsManager extends Service {
       // We don't want to throw an error if the draft fails to save
     }
   }
-
-  willDestroy() {
-    cancel(this?._persistHandler);
-  }
 }
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js
index 8c253066d84..74a8f483546 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-notification-manager.js
@@ -20,6 +20,28 @@ export default class ChatNotificationManager extends Service {
   _subscribedToChat = false;
   _countChatInDocTitle = true;
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+
+    if (!this._shouldRun()) {
+      return;
+    }
+
+    this._chatPresenceChannel.off(
+      "change",
+      this._subscribeToCorrectNotifications
+    );
+    this._chatPresenceChannel.unsubscribe();
+    this._chatPresenceChannel.leave();
+
+    this._corePresenceChannel.off(
+      "change",
+      this._subscribeToCorrectNotifications
+    );
+    this._corePresenceChannel.unsubscribe();
+    this._corePresenceChannel.leave();
+  }
+
   start() {
     if (!this._shouldRun()) {
       return;
@@ -52,28 +74,6 @@ export default class ChatNotificationManager extends Service {
     );
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-
-    if (!this._shouldRun()) {
-      return;
-    }
-
-    this._chatPresenceChannel.off(
-      "change",
-      this._subscribeToCorrectNotifications
-    );
-    this._chatPresenceChannel.unsubscribe();
-    this._chatPresenceChannel.leave();
-
-    this._corePresenceChannel.off(
-      "change",
-      this._subscribeToCorrectNotifications
-    );
-    this._corePresenceChannel.unsubscribe();
-    this._corePresenceChannel.leave();
-  }
-
   shouldCountChatInDocTitle() {
     return this._countChatInDocTitle;
   }
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-tracking-state-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-tracking-state-manager.js
index 2ba505b8e2f..ddb2c2738db 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-tracking-state-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-tracking-state-manager.js
@@ -23,6 +23,11 @@ export default class ChatTrackingStateManager extends Service {
 
   // NOTE: In future, we may want to preload some thread tracking state
   // as well, but for now we do that on demand when the user opens a channel,
+  willDestroy() {
+    super.willDestroy(...arguments);
+    cancel(this._onTriggerNotificationDebounceHandler);
+  }
+
   // to avoid having to load all the threads across all channels into memory at once.
   setupWithPreloadedState({ channel_tracking = {} }) {
     this.chatChannelsManager.channels.forEach((channel) => {
@@ -87,11 +92,6 @@ export default class ChatTrackingStateManager extends Service {
     }, 0);
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-    cancel(this._onTriggerNotificationDebounceHandler);
-  }
-
   /**
    * Some reactivity in the app such as the document title
    * updates are only done via appEvents -- rather than
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat.js b/plugins/chat/assets/javascripts/discourse/services/chat.js
index 64e8b3204ae..f976fb7615e 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat.js
@@ -43,6 +43,29 @@ export default class Chat extends Service {
   @tracked _activeMessage = null;
   @tracked _activeChannel = null;
 
+  init() {
+    super.init(...arguments);
+
+    if (this.userCanChat) {
+      this.presenceChannel = this.presence.getChannel("/chat/online");
+
+      onPresenceChange({
+        callback: this.onPresenceChangeCallback,
+        browserHiddenTime: 150000,
+        userUnseenTime: 150000,
+      });
+    }
+  }
+
+  willDestroy() {
+    super.willDestroy(...arguments);
+
+    if (this.userCanChat) {
+      this.chatSubscriptionsManager.stopChannelsSubscriptions();
+      removeOnPresenceChange(this.onPresenceChangeCallback);
+    }
+  }
+
   get activeChannel() {
     return this._activeChannel;
   }
@@ -94,20 +117,6 @@ export default class Chat extends Service {
     }
   }
 
-  init() {
-    super.init(...arguments);
-
-    if (this.userCanChat) {
-      this.presenceChannel = this.presence.getChannel("/chat/online");
-
-      onPresenceChange({
-        callback: this.onPresenceChangeCallback,
-        browserHiddenTime: 150000,
-        userUnseenTime: 150000,
-      });
-    }
-  }
-
   @bind
   onPresenceChangeCallback(present) {
     if (present) {
@@ -245,15 +254,6 @@ export default class Chat extends Service {
     );
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-
-    if (this.userCanChat) {
-      this.chatSubscriptionsManager.stopChannelsSubscriptions();
-      removeOnPresenceChange(this.onPresenceChangeCallback);
-    }
-  }
-
   updatePresence() {
     next(() => {
       if (this.isDestroyed || this.isDestroying) {
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js b/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js
index e099f522b34..09e8600aafa 100644
--- a/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js
+++ b/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js
@@ -8,6 +8,10 @@ const KEEP_ALIVE_DURATION_SECONDS = 10;
 export default class ComposerPresenceManager extends Service {
   @service presence;
 
+  willDestroy() {
+    this.leave();
+  }
+
   notifyState(intent, id) {
     if (
       this.siteSettings.allow_users_to_hide_profile &&
@@ -57,8 +61,4 @@ export default class ComposerPresenceManager extends Service {
     this._presentChannel = this.presence.getChannel(channelName);
     this._presentChannel.enter();
   }
-
-  willDestroy() {
-    this.leave();
-  }
 }
diff --git a/plugins/poll/assets/javascripts/discourse/components/poll-breakdown-chart.gjs b/plugins/poll/assets/javascripts/discourse/components/poll-breakdown-chart.gjs
index 04d6244f169..ab03382ddd4 100644
--- a/plugins/poll/assets/javascripts/discourse/components/poll-breakdown-chart.gjs
+++ b/plugins/poll/assets/javascripts/discourse/components/poll-breakdown-chart.gjs
@@ -28,6 +28,14 @@ export default class PollBreakdownChart extends Component {
     this._optionToSlice = {};
   }
 
+  willDestroy() {
+    super.willDestroy(...arguments);
+
+    if (this._chart) {
+      this._chart.destroy();
+    }
+  }
+
   didInsertElement() {
     super.didInsertElement(...arguments);
 
@@ -44,14 +52,6 @@ export default class PollBreakdownChart extends Component {
     }
   }
 
-  willDestroy() {
-    super.willDestroy(...arguments);
-
-    if (this._chart) {
-      this._chart.destroy();
-    }
-  }
-
   @discourseComputed("optionColors", "index")
   colorStyle(optionColors, index) {
     return htmlSafe(`background: ${optionColors[index]};`);
diff --git a/plugins/poll/assets/javascripts/discourse/components/poll-info.gjs b/plugins/poll/assets/javascripts/discourse/components/poll-info.gjs
index ffe5db6cece..2b9d5332d4e 100644
--- a/plugins/poll/assets/javascripts/discourse/components/poll-info.gjs
+++ b/plugins/poll/assets/javascripts/discourse/components/poll-info.gjs
@@ -155,6 +155,7 @@ export default class PollInfoComponent extends Component {
       this.publicTitle
     );
   }
+
   <template>
     <div class="poll-info">
       <div class="poll-info_counts">
diff --git a/plugins/poll/assets/javascripts/discourse/components/poll-results-pie.gjs b/plugins/poll/assets/javascripts/discourse/components/poll-results-pie.gjs
index 967f73a290c..8eaebe03eca 100644
--- a/plugins/poll/assets/javascripts/discourse/components/poll-results-pie.gjs
+++ b/plugins/poll/assets/javascripts/discourse/components/poll-results-pie.gjs
@@ -104,6 +104,7 @@ export default class PollResultsPieComponent extends Component {
   registerCanvasElement = modifier((element) => {
     this.canvasElement = element;
   });
+
   get canvasId() {
     return htmlSafe(`poll-results-chart-${this.args.id}`);
   }
@@ -125,6 +126,7 @@ export default class PollResultsPieComponent extends Component {
     // eslint-disable-next-line no-undef
     this._chart = new Chart(el.getContext("2d"), config);
   }
+
   <template>
     <div class="poll-results-chart">
       <canvas
diff --git a/plugins/poll/assets/javascripts/discourse/components/poll-results-standard.gjs b/plugins/poll/assets/javascripts/discourse/components/poll-results-standard.gjs
index ef7772df5bf..5de41c2b25c 100644
--- a/plugins/poll/assets/javascripts/discourse/components/poll-results-standard.gjs
+++ b/plugins/poll/assets/javascripts/discourse/components/poll-results-standard.gjs
@@ -72,6 +72,7 @@ export default class PollResultsStandardComponent extends Component {
   get isMultiple() {
     return this.args.pollType === "multiple";
   }
+
   <template>
     <ul class="results">
       {{#each this.orderedOptions key="voters" as |option|}}
diff --git a/plugins/poll/assets/javascripts/discourse/components/poll-results-tabs.gjs b/plugins/poll/assets/javascripts/discourse/components/poll-results-tabs.gjs
index 9a6d710aab4..263eb4a7449 100644
--- a/plugins/poll/assets/javascripts/discourse/components/poll-results-tabs.gjs
+++ b/plugins/poll/assets/javascripts/discourse/components/poll-results-tabs.gjs
@@ -20,6 +20,7 @@ export default class TabsComponent extends Component {
         ? this.tabs[1]
         : this.tabs[0];
   }
+
   get tabs() {
     let tabs = [];
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b094cc0c1cb..3a37b6f391f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,8 +32,8 @@ importers:
         specifier: ^7.25.9
         version: 7.25.9(@babel/core@7.26.0)
       '@discourse/lint-configs':
-        specifier: ^2.1.0
-        version: 2.1.0(ember-template-lint@6.0.0)(eslint@9.14.0)(prettier@2.8.8)
+        specifier: ^2.2.0
+        version: 2.2.0(ember-template-lint@6.0.0)(eslint@9.14.0)(prettier@2.8.8)
       '@discourse/moment-timezone-names-translations':
         specifier: ^1.0.0
         version: 1.0.0
@@ -1836,8 +1836,8 @@ packages:
   '@discourse/itsatrap@2.0.10':
     resolution: {integrity: sha512-Jn1gdiyHMGUsmUfLFf4Q7VnTAv0l7NePbegU6pKhKHEmbzV3FosGxq30fTOYgVyTS1bxqGjlA6LvQttJpv3ROw==}
 
-  '@discourse/lint-configs@2.1.0':
-    resolution: {integrity: sha512-VoZ/+kV1F/nO+bdKN3a/LSFcJTXGwUUVw3yaJiiJCc/+k4fcPaOgcI3z+Ow0ld+DPFZUVkqmxj18r86ThCLyCA==}
+  '@discourse/lint-configs@2.2.0':
+    resolution: {integrity: sha512-lj13X+3/DRV2ZBQe3eJvxOO23e87DPfUSSqm0UPfP04VJ7141BHwWn9VVF0rOr+bMe2eiirsqlg2AbMn7gMb+A==}
     peerDependencies:
       ember-template-lint: 6.0.0
       eslint: ^9.14.0
@@ -2547,6 +2547,12 @@ packages:
   '@socket.io/component-emitter@3.1.2':
     resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
 
+  '@stylistic/eslint-plugin-js@2.11.0':
+    resolution: {integrity: sha512-btchD0P3iij6cIk5RR5QMdEhtCCV0+L6cNheGhGCd//jaHILZMTi/EOqgEDAf1s4ZoViyExoToM+S2Iwa3U9DA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: '>=8.40.0'
+
   '@swc/core-darwin-arm64@1.9.2':
     resolution: {integrity: sha512-nETmsCoY29krTF2PtspEgicb3tqw7Ci5sInTI03EU5zpqYbPjoPH99BVTjj0OsF53jP5MxwnLI5Hm21lUn1d6A==}
     engines: {node: '>=10'}
@@ -9341,11 +9347,12 @@ snapshots:
 
   '@discourse/itsatrap@2.0.10': {}
 
-  '@discourse/lint-configs@2.1.0(ember-template-lint@6.0.0)(eslint@9.14.0)(prettier@2.8.8)':
+  '@discourse/lint-configs@2.2.0(ember-template-lint@6.0.0)(eslint@9.14.0)(prettier@2.8.8)':
     dependencies:
       '@babel/core': 7.26.0(supports-color@8.1.1)
       '@babel/eslint-parser': 7.25.8(@babel/core@7.26.0)(eslint@9.14.0)
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
+      '@stylistic/eslint-plugin-js': 2.11.0(eslint@9.14.0)
       ember-template-lint: 6.0.0
       eslint: 9.14.0
       eslint-plugin-decorator-position: 6.0.0(@babel/eslint-parser@7.25.8(@babel/core@7.26.0)(eslint@9.14.0))(eslint@9.14.0)
@@ -10306,6 +10313,12 @@ snapshots:
 
   '@socket.io/component-emitter@3.1.2': {}
 
+  '@stylistic/eslint-plugin-js@2.11.0(eslint@9.14.0)':
+    dependencies:
+      eslint: 9.14.0
+      eslint-visitor-keys: 4.2.0
+      espree: 10.3.0
+
   '@swc/core-darwin-arm64@1.9.2':
     optional: true