diff --git a/aleksis/core/assets/app/apollo.js b/aleksis/core/assets/app/apollo.js
index 99f72137596e0cf32eb7f3fbb1c535aa02d5d960..5d0860f7c8ad5f5d81994684b353e3c824ea9cd9 100644
--- a/aleksis/core/assets/app/apollo.js
+++ b/aleksis/core/assets/app/apollo.js
@@ -1,5 +1,8 @@
+/*
+ * Configuration for Apollo provider, client, and caches.
+ */
+
 import { ApolloClient, HttpLink, from } from "@/apollo-boost";
-import VueApollo from "@/vue-apollo";
 
 import { onError } from "@/apollo-link-error";
 import { RetryLink } from "@/apollo-link-retry";
@@ -10,8 +13,17 @@ import gql from "@/graphql-tag";
 
 import gqlSnackbarItems from "../components/app/snackbarItems.graphql";
 
+// Cache for GraphQL query results in memory and persistent across sessions
 const cache = new InMemoryCache();
+await persistCache({
+  cache: cache,
+  storage: new LocalStorageWrapper(window.localStorage),
+});
 
+/**
+ * Type definitions for Apollo/s local state management
+ *  cf. https://www.apollographql.com/docs/react/local-state/local-state-management/
+ */
 const typeDefs = gql`
   type snackbarItem {
     id: ID!
@@ -26,28 +38,27 @@ const typeDefs = gql`
   }
 
   type Mutation {
-    checkSnackbarItem(id: ID!): Boolean
-    setLoading(state: Boolean!): Boolean
+    toggleSnackbarItem(id: ID!): Boolean
   }
 `;
 
+/** Resolvers for local state management */
 const resolvers = {
   Mutation: {
-    // eslint-disable-next-line no-unused-vars
-    checkSnackbarItem: (_, { id }, { cache }) => {
+    /** Toggle snackbar item read or unread, given its ID */
+    toggleSnackbarItem: (_, { id }, { cache }) => {
       const data = cache.readQuery({ query: gqlSnackbarItems });
       const currentItem = data.snackbarItems.find((item) => item.id === id);
       currentItem.read = !currentItem.read;
       cache.writeQuery({ query: gqlSnackbarItems, data });
       return currentItem.read;
     },
-    // eslint-disable-next-line
-    setLoading: (_, { state }, {}) => {
-      return true;
-    },
   },
 };
 
+/**
+ * Utility function to add a snackbar on GraphQL errors.
+ */
 function addErrorSnackbarItem(messageKey) {
   let uuid = crypto.randomUUID();
   cache.writeQuery({
@@ -69,28 +80,32 @@ function addErrorSnackbarItem(messageKey) {
   });
 }
 
-const retryLink = new RetryLink();
-
-const errorLink = onError(({ graphQLErrors, networkError }) => {
-  if (graphQLErrors) addErrorSnackbarItem("graphql.snackbar_error_message");
-
-  if (networkError) app.offline = true;
-});
-
-const httpLink = new HttpLink({
-  uri: window.location.origin + "/graphql/",
-});
-
-await persistCache({
-  cache: cache,
-  storage: new LocalStorageWrapper(window.localStorage),
-});
-
+// Define Apollo links for handling query operations.
+const links = [
+  // Automatically retry failed queries
+  new RetryLink(),
+  // Add custom error handlers
+  onError(({ graphQLErrors, networkError }) => {
+    // Add a snackbar on all errors returned by the GraphQL endpoint
+    if (graphQLErrors) addErrorSnackbarItem("graphql.snackbar_error_message");
+    // Set app offline globally on network errors
+    //  This will cause the offline logic to kick in, starting a ping check or
+    //  similar recovery strategies depending on the app/navigator state
+    if (networkError) app.offline = true;
+  }),
+  // Finally, the HTTP link to the real backend (Django)
+  new HttpLink({
+    uri: window.location.origin + "/graphql/",
+  }),
+];
+
+
+/** Upstream Apollo GraphQL client */
 const apolloClient = new ApolloClient({
   typeDefs,
   resolvers,
   cache,
-  link: from([retryLink, errorLink, httpLink]),
+  link: from(links),
 });
 
 const apolloOpts = {
diff --git a/aleksis/core/assets/app/dateTimeFormats.js b/aleksis/core/assets/app/dateTimeFormats.js
index eb9131f979e96413e9f869255dc960022fb9d592..a835b238ccb56b0cdfc465f3b7ea2341e45322f8 100644
--- a/aleksis/core/assets/app/dateTimeFormats.js
+++ b/aleksis/core/assets/app/dateTimeFormats.js
@@ -1,3 +1,4 @@
+/** Date.time formats for VueI18n */
 const dateTimeFormats = {
   en: {
     short: {
diff --git a/aleksis/core/assets/app/i18n.js b/aleksis/core/assets/app/i18n.js
index 4ff38f759889edaddaa1ef00c09f1c8605ddb131..ed50743eddf2d7fe306b9fe1116ef17fc914911d 100644
--- a/aleksis/core/assets/app/i18n.js
+++ b/aleksis/core/assets/app/i18n.js
@@ -1,3 +1,7 @@
+/*
+ * Configuration for VueI18n
+ */
+
 import dateTimeFormats from "./dateTimeFormats.js";
 
 const i18nOpts = {
diff --git a/aleksis/core/assets/app/router.js b/aleksis/core/assets/app/router.js
index e7cb157b3227a70210458f09f453100aa243f61b..b1667866c17472759c9a589dfbe2cad0a9a961eb 100644
--- a/aleksis/core/assets/app/router.js
+++ b/aleksis/core/assets/app/router.js
@@ -1,3 +1,7 @@
+/*
+ * Configuration for Vue router
+ */
+
 import routes from "../routes.js";
 
 const routerOpts = {
diff --git a/aleksis/core/assets/app/vuetify.js b/aleksis/core/assets/app/vuetify.js
index c92924918eaf6fbf0ff3aa79af20696bd2fdb545..e2d4439a6dac88f00af6d7ff57d8bda0b963ea40 100644
--- a/aleksis/core/assets/app/vuetify.js
+++ b/aleksis/core/assets/app/vuetify.js
@@ -1,3 +1,6 @@
+/*
+ * Configuration for Vuetify
+ */
 
 import "@/@mdi/font/css/materialdesignicons.css";
 import "@/vuetify/dist/vuetify.min.css";
diff --git a/aleksis/core/assets/components/app/SnackbarItem.vue b/aleksis/core/assets/components/app/SnackbarItem.vue
index b60ef0e86841c158dd256222d77bc9181ed27210..95eeb13ab10367e5d7576dd22247e6c7c33993da 100644
--- a/aleksis/core/assets/components/app/SnackbarItem.vue
+++ b/aleksis/core/assets/components/app/SnackbarItem.vue
@@ -2,7 +2,7 @@
   <v-snackbar :value="!snackbarItem.read" :color="snackbarItem.color">
     {{ $t(snackbarItem.messageKey) }}
     <template #action="{ attrs }">
-      <v-btn icon @click="checkSnackbarItem(snackbarItem.id)"
+      <v-btn icon @click="toggleSnackbarItem(snackbarItem.id)"
         ><v-icon>mdi-close</v-icon>
       </v-btn>
     </template>
@@ -10,7 +10,7 @@
 </template>
 
 <script>
-import gqlCheckSnackbarItem from "./checkSnackbarItem.graphql";
+import gqlCheckSnackbarItem from "./toggleSnackbarItem.graphql";
 
 export default {
   name: "SnackbarItem",
@@ -21,7 +21,7 @@ export default {
     },
   },
   methods: {
-    checkSnackbarItem(id) {
+    toggleSnackbarItem(id) {
       this.$apollo.mutate({
         mutation: gqlCheckSnackbarItem,
         variables: { id },
diff --git a/aleksis/core/assets/components/app/checkSnackbarItem.graphql b/aleksis/core/assets/components/app/checkSnackbarItem.graphql
deleted file mode 100644
index a7a75a8c0783cffe974c7c5b8a240a0063535a23..0000000000000000000000000000000000000000
--- a/aleksis/core/assets/components/app/checkSnackbarItem.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-mutation ($id: String!) {
-  checkSnackbarItem(id: $id) @client
-}
diff --git a/aleksis/core/assets/components/app/toggleSnackbarItem.graphql b/aleksis/core/assets/components/app/toggleSnackbarItem.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..2badaf3185ce7ecb9c16b8ffd12ba5f6cff0f34b
--- /dev/null
+++ b/aleksis/core/assets/components/app/toggleSnackbarItem.graphql
@@ -0,0 +1,3 @@
+mutation ($id: String!) {
+  toggleSnackbarItem(id: $id) @client
+}
diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js
index 075b29235d9f2099297268cfc63f0a5c3df9f020..cfa1d88390339e9aeba0729d35555a02d9e6e268 100644
--- a/aleksis/core/assets/index.js
+++ b/aleksis/core/assets/index.js
@@ -1,3 +1,11 @@
+/*
+ * Main entrypoint of AlekSIS0-ore.
+ * 
+ * This script sets up all necessary Vue plugins and defines the Vue app.
+ * For convenience, the Vue app instance will be made globally available
+ * as window.app.
+ */
+
 import Vue from "vue";
 import Vuetify from "@/vuetify";
 import VueI18n from "@/vue-i18n";
@@ -6,15 +14,18 @@ import VueApollo from "@/vue-apollo";
 
 import AleksisVue from "./plugins/aleksis.js";
 
+// Install the AleksisVue plugin first and let it do early setup
 Vue.use(AleksisVue);
 Vue.$configureSentry();
 Vue.$registerGlobalComponents();
 
+// Third-party plugins
 Vue.use(Vuetify);
 Vue.use(VueI18n);
 Vue.use(VueRouter);
 Vue.use(VueApollo);
 
+// All of these imports yield config objects to be passed to the plugin constructors
 import vuetifyOpts from "./app/vuetify.js";
 import i18nOpts from "./app/i18n.js";
 import routerOpts from "./app/router.js";
@@ -25,6 +36,7 @@ const vuetify = new Vuetify(vuetifyOpts);
 const router = new VueRouter(routerOpts);
 const apolloProvider = new VueApollo(apolloOpts);
 
+// Parent component rendering the UI and all features outside the specific pages
 import App from "./components/app/App.vue";
 
 const app = new Vue({
@@ -41,6 +53,7 @@ const app = new Vue({
   i18n,
 });
 
+// Late setup for some plugins handed off to out ALeksisVue plugin
 app.$loadAppMessages();
 app.$setupNavigationGuards();
 
diff --git a/aleksis/core/assets/mixins/useRegisterSW.js b/aleksis/core/assets/mixins/useRegisterSW.js
index 1034799fa5159c7a10d27457a39454202ccf84ba..e7659fca1f28c41f0ca1ee7feb4c6aeef458e525 100644
--- a/aleksis/core/assets/mixins/useRegisterSW.js
+++ b/aleksis/core/assets/mixins/useRegisterSW.js
@@ -1,4 +1,8 @@
-export default {
+/**
+ * Vue mixin to register the PWA service worker once the main
+ * component gets ready.
+ */
+const useRegisterSWMixin = {
   name: "useRegisterSW",
   data() {
     return {
@@ -55,3 +59,5 @@ export default {
     },
   },
 };
+
+export default useRegisterSWMixin;
diff --git a/aleksis/core/assets/plugins/aleksis.js b/aleksis/core/assets/plugins/aleksis.js
index 2ae3085a38dba4d12294bca9e9069fe7188a0692..a1bed3845bfdd8fcd67900ef507277410a9503d2 100644
--- a/aleksis/core/assets/plugins/aleksis.js
+++ b/aleksis/core/assets/plugins/aleksis.js
@@ -1,14 +1,29 @@
+/*
+ * Plugin to collect AlekSIS-specific Vue utilities.
+ */
+
+// aleksisAppImporter is a virtual module defined in Vite config
 import { appObjects, appMessages } from "aleksisAppImporter";
 
 import MessageBox from "../components/MessageBox.vue";
 
-const AleksisVue = {};
-
 console.debug("Defining AleksisVue plugin");
+const AleksisVue = {};
 
 AleksisVue.install = function (Vue, options) {
+  /**
+   * The browser title when the app was loaded.
+   * 
+   * Thus, it is injected from Django in the vue_index template.
+   */
   Vue.$pageBaseTitle = document.title;
 
+  /**
+   * Configure Sentry if desired.
+   * 
+   * It depends on Sentry settings being passed as a DOM object by Django
+   * in the vue_index template.
+   */
   Vue.$configureSentry = function () {
     if (document.getElementById("sentry_settings") !== null) {
       const Sentry = import("@sentry/vue");
@@ -32,9 +47,21 @@ AleksisVue.install = function (Vue, options) {
     }
   };
 
+  /**
+   * Register all global components that shall be reusable by apps.
+   */
   Vue.$registerGlobalComponents = function () {
-    Vue.component(MessageBox.name, MessageBox);
+    Vue.component("message-box", () => import("../components/MessageBox.vue"));
   };
+
+  /**
+   * Set the page title.
+   * 
+   * This will automatically add the base title discovered at app loading time.
+   * 
+   * @param {string} title Specific title to set, or null.
+   * @param {Object} route Route to discover title from, or null.
+   */
   Vue.prototype.$setPageTitle = function (title, route) {
     let titleParts = [];
 
@@ -55,6 +82,9 @@ AleksisVue.install = function (Vue, options) {
     document.title = newTitle;
   };
 
+  /**
+   * Load i18n messages from all known AlekSIS apps.
+   */
   Vue.prototype.$loadAppMessages = function () {
     console.log(this);
     for (const messages of Object.values(appMessages)) {
@@ -64,9 +94,13 @@ AleksisVue.install = function (Vue, options) {
     }
   };
 
+  /**
+   * Add navigation guards to account for global loading state and page titles.
+   */
   Vue.prototype.$setupNavigationGuards = function () {
     // eslint-disable-next-line no-unused-vars
     this.$router.afterEach((to, from, next) => {
+      console.debug("Setting new page title due to route change");
       window.app.$setPageTitle(null, to);
     });
 
@@ -80,13 +114,6 @@ AleksisVue.install = function (Vue, options) {
     this.$router.afterEach((to, from) => {
       window.app.contentLoading = false;
     });
-
-    // eslint-disable-next-line no-unused-vars
-    this.$router.beforeEach((to, from, next) => {
-      console.debug("Setting new page title due to route change");
-      window.app.$setPageTitle(null, to);
-      next();
-    });
   };
 };
 
diff --git a/aleksis/core/assets/routeValidators.js b/aleksis/core/assets/routeValidators.js
index 5db52731038d43e9e941483dab8906de9a88763d..af9a2ad41c4c98e6c62c116227a4ba7c4fef7d05 100644
--- a/aleksis/core/assets/routeValidators.js
+++ b/aleksis/core/assets/routeValidators.js
@@ -1,3 +1,9 @@
+/**
+ * Check whether the user is logged in on the AlekSIS server.
+ * 
+ * @param {Object} whoAmI The person object as returned by the whoAmI query
+ * @returns true if the user is logged in, false if not
+ */
 const notLoggedInValidator = (whoAmI) => {
   return !whoAmI || whoAmI.isAnonymous;
 };
diff --git a/aleksis/core/assets/routes.js b/aleksis/core/assets/routes.js
index 50a24935676d38982bf48ea14d05598ed50f054e..064e913ccb285d505210b96a8b15e432d0a88500 100644
--- a/aleksis/core/assets/routes.js
+++ b/aleksis/core/assets/routes.js
@@ -1,3 +1,11 @@
+/*
+ * Vue router definitions for all of AlekSIS.
+ * 
+ * This module defines the routes of AlekSIS-Core and also loads
+ * and adds all routes from known apps.
+ */
+
+// aleksisAppImporter is a virtual module defined in Vite config
 import { appObjects } from "aleksisAppImporter";
 
 import { notLoggedInValidator } from "./routeValidators";
@@ -772,6 +780,7 @@ for (const [appName, appRoutes] of Object.entries(appObjects)) {
   });
 }
 
+// Fallback route defined last to ensure 404 view works
 routes.push({
   path: "/*",
   component: () => import("./components/app/Error404.vue"),