diff --git a/assets/eslint.config.js b/assets/eslint.config.js
index 51cc1e26..c39019e8 100644
--- a/assets/eslint.config.js
+++ b/assets/eslint.config.js
@@ -1,5 +1,5 @@
 import tsEslint from 'typescript-eslint';
-import vitestPlugin from 'eslint-plugin-vitest';
+import vitestPlugin from '@vitest/eslint-plugin';
 import globals from 'globals';
 
 export default tsEslint.config(
diff --git a/assets/js/autocomplete/__tests__/context.ts b/assets/js/autocomplete/__tests__/context.ts
index 5bf636aa..c7e5369c 100644
--- a/assets/js/autocomplete/__tests__/context.ts
+++ b/assets/js/autocomplete/__tests__/context.ts
@@ -146,7 +146,7 @@ export class TestContext {
     await vi.runAllTimersAsync();
   }
 
-  async keyDown(code: string, params?: { ctrlKey?: boolean }) {
+  async keyDown(code: string, params?: { ctrlKey?: boolean; key?: string }) {
     fireEvent.keyDown(this.input, { code, ...(params ?? {}) });
     await vi.runAllTimersAsync();
   }
diff --git a/assets/js/autocomplete/__tests__/keyboard.spec.ts b/assets/js/autocomplete/__tests__/keyboard.spec.ts
index 11a3d446..3e135034 100644
--- a/assets/js/autocomplete/__tests__/keyboard.spec.ts
+++ b/assets/js/autocomplete/__tests__/keyboard.spec.ts
@@ -1,114 +1,146 @@
-import { init } from './context';
+import { init, TestContext } from './context';
 
-it('supports navigation via keyboard', async () => {
-  const ctx = await init();
+describe('Autocomplete keyboard navigation', () => {
+  let ctx: TestContext;
 
-  await ctx.setInput('f');
+  beforeAll(async () => {
+    ctx = await init();
+  });
 
-  await ctx.keyDown('ArrowDown');
+  it('supports navigation via keyboard', async () => {
+    await ctx.setInput('f');
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest<>",
-      "suggestions": [
-        "👉 forest  3",
-        "force field  1",
-        "fog  1",
-        "flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown');
 
-  await ctx.keyDown('ArrowDown');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [
+          "👉 forest  3",
+          "force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "force field<>",
-      "suggestions": [
-        "forest  3",
-        "👉 force field  1",
-        "fog  1",
-        "flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown');
 
-  await ctx.keyDown('ArrowDown', { ctrlKey: true });
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "force field<>",
+        "suggestions": [
+          "forest  3",
+          "👉 force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "flower<>",
-      "suggestions": [
-        "forest  3",
-        "force field  1",
-        "fog  1",
-        "👉 flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown', { ctrlKey: true });
 
-  await ctx.keyDown('ArrowUp', { ctrlKey: true });
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "flower<>",
+        "suggestions": [
+          "forest  3",
+          "force field  1",
+          "fog  1",
+          "👉 flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest<>",
-      "suggestions": [
-        "👉 forest  3",
-        "force field  1",
-        "fog  1",
-        "flower  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowUp', { ctrlKey: true });
 
-  await ctx.keyDown('Enter');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [
+          "👉 forest  3",
+          "force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest<>",
-      "suggestions": [],
-    }
-  `);
+    await ctx.keyDown('Enter');
 
-  await ctx.setInput('forest, t<>, safe');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, t<>, safe",
-      "suggestions": [
-        "artist:test  1",
-      ],
-    }
-  `);
+    await ctx.setInput('forest, t<>, safe');
 
-  await ctx.keyDown('ArrowDown');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, t<>, safe",
+        "suggestions": [
+          "artist:test  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, artist:test<>, safe",
-      "suggestions": [
-        "👉 artist:test  1",
-      ],
-    }
-  `);
+    await ctx.keyDown('ArrowDown');
 
-  await ctx.keyDown('Escape');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, artist:test<>, safe",
+        "suggestions": [
+          "👉 artist:test  1",
+        ],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, artist:test<>, safe",
-      "suggestions": [],
-    }
-  `);
+    await ctx.keyDown('Escape');
 
-  await ctx.setInput('forest, t<>, safe');
-  await ctx.keyDown('ArrowDown');
-  await ctx.keyDown('Enter');
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, artist:test<>, safe",
+        "suggestions": [],
+      }
+    `);
 
-  ctx.expectUi().toMatchInlineSnapshot(`
-    {
-      "input": "forest, artist:test<>, safe",
-      "suggestions": [],
-    }
-  `);
+    await ctx.setInput('forest, t<>, safe');
+    await ctx.keyDown('ArrowDown');
+    await ctx.keyDown('Enter');
+
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest, artist:test<>, safe",
+        "suggestions": [],
+      }
+    `);
+  });
+
+  it('should handle Enter key presses with empty code property', async () => {
+    await ctx.setInput('f');
+
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "f<>",
+        "suggestions": [
+          "forest  3",
+          "force field  1",
+          "fog  1",
+          "flower  1",
+        ],
+      }
+    `);
+
+    await ctx.keyDown('ArrowDown');
+    await ctx.keyDown('', { key: 'Enter' });
+
+    ctx.expectUi().toMatchInlineSnapshot(`
+      {
+        "input": "forest<>",
+        "suggestions": [],
+      }
+    `);
+  });
 });
diff --git a/assets/js/autocomplete/index.ts b/assets/js/autocomplete/index.ts
index 8bffa7d0..0acc4846 100644
--- a/assets/js/autocomplete/index.ts
+++ b/assets/js/autocomplete/index.ts
@@ -2,12 +2,12 @@ import { LocalAutocompleter } from '../utils/local-autocompleter';
 import * as history from './history';
 import { AutocompletableInput, TextInputElement } from './input';
 import {
-  SuggestionsPopupComponent,
-  Suggestions,
-  TagSuggestionComponent,
-  Suggestion,
   HistorySuggestionComponent,
   ItemSelectedEvent,
+  Suggestion,
+  Suggestions,
+  SuggestionsPopupComponent,
+  TagSuggestionComponent,
 } from '../utils/suggestions-view';
 import { prefixMatchParts } from '../utils/suggestions-model';
 import { $$ } from '../utils/dom';
@@ -243,13 +243,21 @@ class Autocomplete {
     if (!this.isActive() || this.input.element !== event.target) {
       return;
     }
-    if ((event.key === ',' || event.code === 'Enter') && this.input.type === 'single-tag') {
+
+    let keyCode = event.code;
+
+    // Chrome & Firefox on Android devices return empty code when "Enter" is pressed.
+    if (!keyCode && event.key === 'Enter') {
+      keyCode = event.key;
+    }
+
+    if ((event.key === ',' || keyCode === 'Enter') && this.input.type === 'single-tag') {
       // Comma/Enter mean the end of input for the current tag in single-tag mode.
       this.hidePopup(`The user accepted the existing input via key: '${event.key}', code: '${event.code}'`);
       return;
     }
 
-    switch (event.code) {
+    switch (keyCode) {
       case 'Enter': {
         const { selectedSuggestion } = this.popup;
         if (!selectedSuggestion) {
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 52ea697b..4e82d090 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -21,11 +21,11 @@
         "@testing-library/dom": "^10.4.0",
         "@testing-library/jest-dom": "^6.6.3",
         "@vitest/coverage-v8": "^2.1.9",
+        "@vitest/eslint-plugin": "^1.1.38",
         "eslint": "^9.16.0",
-        "eslint-plugin-vitest": "^0.5.4",
         "stylelint": "^16.11.0",
         "stylelint-config-standard": "^36.0.1",
-        "typescript-eslint": "8.17.0",
+        "typescript-eslint": "8.29.0",
         "vitest": "^2.1.9",
         "vitest-fetch-mock": "^0.4.2"
       }
@@ -1443,20 +1443,20 @@
       "integrity": "sha512-jxXqkf4sBn/WV9YsOlB5fFzWo9kGafMDF62VmVC1mFF367BuRn/2txr0ZaEchPsNIvyiLckMpxO6Xz3knpC6Nw=="
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz",
-      "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz",
+      "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.17.0",
-        "@typescript-eslint/type-utils": "8.17.0",
-        "@typescript-eslint/utils": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0",
+        "@typescript-eslint/scope-manager": "8.29.0",
+        "@typescript-eslint/type-utils": "8.29.0",
+        "@typescript-eslint/utils": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
-        "ts-api-utils": "^1.3.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1467,24 +1467,20 @@
       },
       "peerDependencies": {
         "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz",
-      "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz",
+      "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.17.0",
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/typescript-estree": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0",
+        "@typescript-eslint/scope-manager": "8.29.0",
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/typescript-estree": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1495,22 +1491,18 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz",
-      "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz",
+      "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0"
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1521,15 +1513,15 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz",
-      "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz",
+      "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.17.0",
-        "@typescript-eslint/utils": "8.17.0",
+        "@typescript-eslint/typescript-estree": "8.29.0",
+        "@typescript-eslint/utils": "8.29.0",
         "debug": "^4.3.4",
-        "ts-api-utils": "^1.3.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1539,18 +1531,14 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
-      "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz",
+      "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1561,19 +1549,19 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
-      "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz",
+      "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/visitor-keys": "8.17.0",
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/visitor-keys": "8.29.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
         "minimatch": "^9.0.4",
         "semver": "^7.6.0",
-        "ts-api-utils": "^1.3.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1582,10 +1570,8 @@
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
@@ -1613,15 +1599,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz",
-      "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz",
+      "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.17.0",
-        "@typescript-eslint/types": "8.17.0",
-        "@typescript-eslint/typescript-estree": "8.17.0"
+        "@typescript-eslint/scope-manager": "8.29.0",
+        "@typescript-eslint/types": "8.29.0",
+        "@typescript-eslint/typescript-estree": "8.29.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1631,21 +1617,17 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
-      "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz",
+      "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.17.0",
+        "@typescript-eslint/types": "8.29.0",
         "eslint-visitor-keys": "^4.2.0"
       },
       "engines": {
@@ -1689,6 +1671,27 @@
         }
       }
     },
+    "node_modules/@vitest/eslint-plugin": {
+      "version": "1.1.38",
+      "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.38.tgz",
+      "integrity": "sha512-KcOTZyVz8RiM5HyriiDVrP1CyBGuhRxle+lBsmSs6NTJEO/8dKVAq+f5vQzHj1/Kc7bYXSDO6yBe62Zx0t5iaw==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@typescript-eslint/utils": "^8.24.0",
+        "eslint": ">= 8.57.0",
+        "typescript": ">= 5.0.0",
+        "vitest": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        },
+        "vitest": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@vitest/expect": {
       "version": "2.1.9",
       "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
@@ -2501,163 +2504,6 @@
         }
       }
     },
-    "node_modules/eslint-plugin-vitest": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz",
-      "integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/utils": "^7.7.1"
-      },
-      "engines": {
-        "node": "^18.0.0 || >= 20.0.0"
-      },
-      "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0",
-        "vitest": "*"
-      },
-      "peerDependenciesMeta": {
-        "@typescript-eslint/eslint-plugin": {
-          "optional": true
-        },
-        "vitest": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
-      "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "7.18.0",
-        "@typescript-eslint/visitor-keys": "7.18.0"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/types": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
-      "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
-      "dev": true,
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
-      "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "7.18.0",
-        "@typescript-eslint/visitor-keys": "7.18.0",
-        "debug": "^4.3.4",
-        "globby": "^11.1.0",
-        "is-glob": "^4.0.3",
-        "minimatch": "^9.0.4",
-        "semver": "^7.6.0",
-        "ts-api-utils": "^1.3.0"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
-      "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
-      "dev": true,
-      "dependencies": {
-        "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "7.18.0",
-        "@typescript-eslint/types": "7.18.0",
-        "@typescript-eslint/typescript-estree": "7.18.0"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "^8.56.0"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
-      "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "7.18.0",
-        "eslint-visitor-keys": "^3.4.3"
-      },
-      "engines": {
-        "node": "^18.18.0 || >=20.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/brace-expansion": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
-      "dev": true,
-      "dependencies": {
-        "balanced-match": "^1.0.0"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/eslint-visitor-keys": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
-      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
-      "dev": true,
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/eslint"
-      }
-    },
-    "node_modules/eslint-plugin-vitest/node_modules/minimatch": {
-      "version": "9.0.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
-      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/eslint-scope": {
       "version": "8.2.0",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
@@ -4858,15 +4704,15 @@
       }
     },
     "node_modules/ts-api-utils": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
-      "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+      "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18.12"
       },
       "peerDependencies": {
-        "typescript": ">=4.2.0"
+        "typescript": ">=4.8.4"
       }
     },
     "node_modules/type-check": {
@@ -4894,14 +4740,14 @@
       }
     },
     "node_modules/typescript-eslint": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz",
-      "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==",
+      "version": "8.29.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.0.tgz",
+      "integrity": "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/eslint-plugin": "8.17.0",
-        "@typescript-eslint/parser": "8.17.0",
-        "@typescript-eslint/utils": "8.17.0"
+        "@typescript-eslint/eslint-plugin": "8.29.0",
+        "@typescript-eslint/parser": "8.29.0",
+        "@typescript-eslint/utils": "8.29.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4911,12 +4757,8 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^8.57.0 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
       }
     },
     "node_modules/undici-types": {
diff --git a/assets/package.json b/assets/package.json
index a2cf1ae2..1477cd88 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -26,11 +26,11 @@
     "@testing-library/dom": "^10.4.0",
     "@testing-library/jest-dom": "^6.6.3",
     "@vitest/coverage-v8": "^2.1.9",
+    "@vitest/eslint-plugin": "^1.1.38",
     "eslint": "^9.16.0",
-    "eslint-plugin-vitest": "^0.5.4",
     "stylelint": "^16.11.0",
     "stylelint-config-standard": "^36.0.1",
-    "typescript-eslint": "8.17.0",
+    "typescript-eslint": "8.29.0",
     "vitest": "^2.1.9",
     "vitest-fetch-mock": "^0.4.2"
   }