diff --git a/Makefile b/Makefile
index f9c02cfbf3..ff327037ea 100644
--- a/Makefile
+++ b/Makefile
@@ -164,9 +164,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
 GO_DIRS := build cmd models modules routers services tests
 WEB_DIRS := web_src/js web_src/css
 
-ESLINT_FILES := web_src/js tools *.js *.mjs tests/e2e/*.js tests/e2e/shared/*.js
 STYLELINT_FILES := web_src/css web_src/js/components/*.vue
-SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml)
+SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.ts *.vue *.md *.yml *.yaml *.toml)
 
 GO_SOURCES := $(wildcard *.go)
 GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
@@ -437,11 +436,11 @@ lint-codespell-fix-i:
 
 .PHONY: lint-js
 lint-js: node_modules
-	npx eslint --color --max-warnings=0 $(ESLINT_FILES)
+	npx eslint --color --max-warnings=0
 
 .PHONY: lint-js-fix
 lint-js-fix: node_modules
-	npx eslint --color --max-warnings=0 $(ESLINT_FILES) --fix
+	npx eslint --color --max-warnings=0 --fix
 
 .PHONY: lint-css
 lint-css: node_modules
diff --git a/eslint.config.mjs b/eslint.config.mjs
index ee2aa5da27..da1b2b5d49 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -14,1148 +14,1156 @@ import wc from 'eslint-plugin-wc';
 import globals from 'globals';
 import vue from 'eslint-plugin-vue';
 import vueScopedCss from 'eslint-plugin-vue-scoped-css';
+import tseslint from 'typescript-eslint';
 
-export default [{
-  ignores: ['web_src/js/vendor', 'web_src/fomantic', 'public/assets/js'],
-}, {
-  plugins: {
-    '@eslint-community/eslint-comments': eslintCommunityEslintPluginEslintComments,
-    '@stylistic/js': stylisticEslintPluginJs,
-    '@vitest': vitest,
-    'array-func': arrayFunc,
-    'import-x': eslintPluginImportX,
-    'no-jquery': noJquery,
-    'no-use-extend-native': noUseExtendNative,
-    regexp,
-    sonarjs,
-    unicorn,
-    playwright,
-    'vitest-globals': vitestGlobals,
-    vue,
-    'vue-scoped-css': vueScopedCss,
-    wc,
+export default tseslint.config(
+  ...tseslint.configs.recommended,
+  eslintPluginImportX.flatConfigs.typescript,
+  {
+    ignores: ['web_src/js/vendor', 'web_src/fomantic', 'public/assets/js'],
   },
-
-  linterOptions: {
-    reportUnusedDisableDirectives: true,
-  },
-
-  languageOptions: {
-    globals: {
-      ...globals.node,
+  {
+    plugins: {
+      '@eslint-community/eslint-comments': eslintCommunityEslintPluginEslintComments,
+      '@stylistic/js': stylisticEslintPluginJs,
+      '@vitest': vitest,
+      'array-func': arrayFunc,
+      'import-x': eslintPluginImportX,
+      'no-jquery': noJquery,
+      'no-use-extend-native': noUseExtendNative,
+      regexp,
+      sonarjs,
+      unicorn,
+      playwright,
+      'vitest-globals': vitestGlobals,
+      vue,
+      'vue-scoped-css': vueScopedCss,
+      wc,
     },
-    parserOptions: {
+
+    linterOptions: {
+      reportUnusedDisableDirectives: true,
+    },
+
+    languageOptions: {
+      globals: {
+        ...globals.node,
+      },
+      parserOptions: {
+        ecmaVersion: 'latest',
+      },
+
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+    },
+    rules: {
+      '@typescript-eslint/no-unused-vars': 'off', // TODO: enable this rule again
+
+      '@eslint-community/eslint-comments/disable-enable-pair': [2],
+      '@eslint-community/eslint-comments/no-aggregating-enable': [2],
+      '@eslint-community/eslint-comments/no-duplicate-disable': [2],
+      '@eslint-community/eslint-comments/no-restricted-disable': [0],
+      '@eslint-community/eslint-comments/no-unlimited-disable': [2],
+      '@eslint-community/eslint-comments/no-unused-disable': [2],
+      '@eslint-community/eslint-comments/no-unused-enable': [2],
+      '@eslint-community/eslint-comments/no-use': [0],
+      '@eslint-community/eslint-comments/require-description': [0],
+      '@stylistic/js/array-bracket-newline': [0],
+      '@stylistic/js/array-bracket-spacing': [2, 'never'],
+      '@stylistic/js/array-element-newline': [0],
+      '@stylistic/js/arrow-parens': [2, 'always'],
+
+      '@stylistic/js/arrow-spacing': [2, {
+        before: true,
+        after: true,
+      }],
+
+      '@stylistic/js/block-spacing': [0],
+
+      '@stylistic/js/brace-style': [2, '1tbs', {
+        allowSingleLine: true,
+      }],
+
+      '@stylistic/js/comma-dangle': [2, 'always-multiline'],
+
+      '@stylistic/js/comma-spacing': [2, {
+        before: false,
+        after: true,
+      }],
+
+      '@stylistic/js/comma-style': [2, 'last'],
+      '@stylistic/js/computed-property-spacing': [2, 'never'],
+      '@stylistic/js/dot-location': [2, 'property'],
+      '@stylistic/js/eol-last': [2],
+      '@stylistic/js/function-call-spacing': [2, 'never'],
+      '@stylistic/js/function-call-argument-newline': [0],
+      '@stylistic/js/function-paren-newline': [0],
+      '@stylistic/js/generator-star-spacing': [0],
+      '@stylistic/js/implicit-arrow-linebreak': [0],
+
+      '@stylistic/js/indent': [2, 2, {
+        ignoreComments: true,
+        SwitchCase: 1,
+      }],
+
+      '@stylistic/js/key-spacing': [2],
+      '@stylistic/js/keyword-spacing': [2],
+      '@stylistic/js/linebreak-style': [2, 'unix'],
+      '@stylistic/js/lines-around-comment': [0],
+      '@stylistic/js/lines-between-class-members': [0],
+      '@stylistic/js/max-len': [0],
+      '@stylistic/js/max-statements-per-line': [0],
+      '@stylistic/js/multiline-ternary': [0],
+      '@stylistic/js/new-parens': [2],
+      '@stylistic/js/newline-per-chained-call': [0],
+      '@stylistic/js/no-confusing-arrow': [0],
+      '@stylistic/js/no-extra-parens': [0],
+      '@stylistic/js/no-extra-semi': [2],
+      '@stylistic/js/no-floating-decimal': [0],
+      '@stylistic/js/no-mixed-operators': [0],
+      '@stylistic/js/no-mixed-spaces-and-tabs': [2],
+
+      '@stylistic/js/no-multi-spaces': [2, {
+        ignoreEOLComments: true,
+
+        exceptions: {
+          Property: true,
+        },
+      }],
+
+      '@stylistic/js/no-multiple-empty-lines': [2, {
+        max: 1,
+        maxEOF: 0,
+        maxBOF: 0,
+      }],
+
+      '@stylistic/js/no-tabs': [2],
+      '@stylistic/js/no-trailing-spaces': [2],
+      '@stylistic/js/no-whitespace-before-property': [2],
+      '@stylistic/js/nonblock-statement-body-position': [2],
+      '@stylistic/js/object-curly-newline': [0],
+      '@stylistic/js/object-curly-spacing': [2, 'never'],
+      '@stylistic/js/object-property-newline': [0],
+      '@stylistic/js/one-var-declaration-per-line': [0],
+      '@stylistic/js/operator-linebreak': [2, 'after'],
+      '@stylistic/js/padded-blocks': [2, 'never'],
+      '@stylistic/js/padding-line-between-statements': [0],
+      '@stylistic/js/quote-props': [0],
+
+      '@stylistic/js/quotes': [2, 'single', {
+        avoidEscape: true,
+        allowTemplateLiterals: true,
+      }],
+
+      '@stylistic/js/rest-spread-spacing': [2, 'never'],
+
+      '@stylistic/js/semi': [2, 'always', {
+        omitLastInOneLineBlock: true,
+      }],
+
+      '@stylistic/js/semi-spacing': [2, {
+        before: false,
+        after: true,
+      }],
+
+      '@stylistic/js/semi-style': [2, 'last'],
+      '@stylistic/js/space-before-blocks': [2, 'always'],
+
+      '@stylistic/js/space-before-function-paren': [2, {
+        anonymous: 'ignore',
+        named: 'never',
+        asyncArrow: 'always',
+      }],
+
+      '@stylistic/js/space-in-parens': [2, 'never'],
+      '@stylistic/js/space-infix-ops': [2],
+      '@stylistic/js/space-unary-ops': [2],
+      '@stylistic/js/spaced-comment': [2, 'always'],
+      '@stylistic/js/switch-colon-spacing': [2],
+      '@stylistic/js/template-curly-spacing': [2, 'never'],
+      '@stylistic/js/template-tag-spacing': [2, 'never'],
+      '@stylistic/js/wrap-iife': [2, 'inside'],
+      '@stylistic/js/wrap-regex': [0],
+      '@stylistic/js/yield-star-spacing': [2, 'after'],
+      'accessor-pairs': [2],
+
+      'array-callback-return': [2, {
+        checkForEach: true,
+      }],
+
+      'array-func/avoid-reverse': [2],
+      'array-func/from-map': [2],
+      'array-func/no-unnecessary-this-arg': [2],
+      'array-func/prefer-array-from': [2],
+      'array-func/prefer-flat-map': [0],
+      'array-func/prefer-flat': [0],
+      'arrow-body-style': [0],
+      'block-scoped-var': [2],
+      camelcase: [0],
+      'capitalized-comments': [0],
+      'class-methods-use-this': [0],
+      complexity: [0],
+      'consistent-return': [0],
+      'consistent-this': [0],
+      'constructor-super': [2],
+      curly: [0],
+      'default-case-last': [2],
+      'default-case': [0],
+      'default-param-last': [0],
+      'dot-notation': [0],
+      eqeqeq: [2],
+      'for-direction': [2],
+      'func-name-matching': [2],
+      'func-names': [0],
+      'func-style': [0],
+      'getter-return': [2],
+      'grouped-accessor-pairs': [2],
+      'guard-for-in': [0],
+      'id-blacklist': [0],
+      'id-length': [0],
+      'id-match': [0],
+      'init-declarations': [0],
+      'line-comment-position': [0],
+      'logical-assignment-operators': [0],
+      'max-classes-per-file': [0],
+      'max-depth': [0],
+      'max-lines-per-function': [0],
+      'max-lines': [0],
+      'max-nested-callbacks': [0],
+      'max-params': [0],
+      'max-statements': [0],
+      'multiline-comment-style': [2, 'separate-lines'],
+      'new-cap': [0],
+      'no-alert': [0],
+      'no-array-constructor': [2],
+      'no-async-promise-executor': [0],
+      'no-await-in-loop': [0],
+      'no-bitwise': [0],
+      'no-buffer-constructor': [0],
+      'no-caller': [2],
+      'no-case-declarations': [2],
+      'no-class-assign': [2],
+      'no-compare-neg-zero': [2],
+      'no-cond-assign': [2, 'except-parens'],
+
+      'no-console': [1, {
+        allow: ['debug', 'info', 'warn', 'error'],
+      }],
+
+      'no-const-assign': [2],
+      'no-constant-binary-expression': [2],
+      'no-constant-condition': [0],
+      'no-constructor-return': [2],
+      'no-continue': [0],
+      'no-control-regex': [0],
+      'no-debugger': [1],
+      'no-delete-var': [2],
+      'no-div-regex': [0],
+      'no-dupe-args': [2],
+      'no-dupe-class-members': [2],
+      'no-dupe-else-if': [2],
+      'no-dupe-keys': [2],
+      'no-duplicate-case': [2],
+      'no-duplicate-imports': [2],
+      'no-else-return': [2],
+      'no-empty-character-class': [2],
+      'no-empty-function': [0],
+      'no-empty-pattern': [2],
+      'no-empty-static-block': [2],
+
+      'no-empty': [2, {
+        allowEmptyCatch: true,
+      }],
+
+      'no-eq-null': [2],
+      'no-eval': [2],
+      'no-ex-assign': [2],
+      'no-extend-native': [2],
+      'no-extra-bind': [2],
+      'no-extra-boolean-cast': [2],
+      'no-extra-label': [0],
+      'no-fallthrough': [2],
+      'no-func-assign': [2],
+      'no-global-assign': [2],
+      'no-implicit-coercion': [2],
+      'no-implicit-globals': [0],
+      'no-implied-eval': [2],
+      'no-import-assign': [2],
+      'no-inline-comments': [0],
+      'no-inner-declarations': [2],
+      'no-invalid-regexp': [2],
+      'no-invalid-this': [0],
+      'no-irregular-whitespace': [2],
+      'no-iterator': [2],
+      'no-jquery/no-ajax-events': [2],
+      'no-jquery/no-ajax': [2],
+      'no-jquery/no-and-self': [2],
+      'no-jquery/no-animate-toggle': [2],
+      'no-jquery/no-animate': [2],
+      'no-jquery/no-append-html': [2],
+      'no-jquery/no-attr': [2],
+      'no-jquery/no-bind': [2],
+      'no-jquery/no-box-model': [2],
+      'no-jquery/no-browser': [2],
+      'no-jquery/no-camel-case': [2],
+      'no-jquery/no-class-state': [2],
+      'no-jquery/no-class': [0],
+      'no-jquery/no-clone': [2],
+      'no-jquery/no-closest': [0],
+      'no-jquery/no-constructor-attributes': [2],
+      'no-jquery/no-contains': [2],
+      'no-jquery/no-context-prop': [2],
+      'no-jquery/no-css': [2],
+      'no-jquery/no-data': [0],
+      'no-jquery/no-deferred': [2],
+      'no-jquery/no-delegate': [2],
+      'no-jquery/no-each-collection': [0],
+      'no-jquery/no-each-util': [0],
+      'no-jquery/no-each': [0],
+      'no-jquery/no-error-shorthand': [2],
+      'no-jquery/no-error': [2],
+      'no-jquery/no-escape-selector': [2],
+      'no-jquery/no-event-shorthand': [2],
+      'no-jquery/no-extend': [2],
+      'no-jquery/no-fade': [2],
+      'no-jquery/no-filter': [0],
+      'no-jquery/no-find-collection': [0],
+      'no-jquery/no-find-util': [2],
+      'no-jquery/no-find': [0],
+      'no-jquery/no-fx-interval': [2],
+      'no-jquery/no-global-eval': [2],
+      'no-jquery/no-global-selector': [0],
+      'no-jquery/no-grep': [2],
+      'no-jquery/no-has': [2],
+      'no-jquery/no-hold-ready': [2],
+      'no-jquery/no-html': [0],
+      'no-jquery/no-in-array': [2],
+      'no-jquery/no-is-array': [2],
+      'no-jquery/no-is-empty-object': [2],
+      'no-jquery/no-is-function': [2],
+      'no-jquery/no-is-numeric': [2],
+      'no-jquery/no-is-plain-object': [2],
+      'no-jquery/no-is-window': [2],
+      'no-jquery/no-is': [2],
+      'no-jquery/no-jquery-constructor': [0],
+      'no-jquery/no-live': [2],
+      'no-jquery/no-load-shorthand': [2],
+      'no-jquery/no-load': [2],
+      'no-jquery/no-map-collection': [0],
+      'no-jquery/no-map-util': [2],
+      'no-jquery/no-map': [2],
+      'no-jquery/no-merge': [2],
+      'no-jquery/no-node-name': [2],
+      'no-jquery/no-noop': [2],
+      'no-jquery/no-now': [2],
+      'no-jquery/no-on-ready': [2],
+      'no-jquery/no-other-methods': [0],
+      'no-jquery/no-other-utils': [2],
+      'no-jquery/no-param': [2],
+      'no-jquery/no-parent': [0],
+      'no-jquery/no-parents': [2],
+      'no-jquery/no-parse-html-literal': [2],
+      'no-jquery/no-parse-html': [2],
+      'no-jquery/no-parse-json': [2],
+      'no-jquery/no-parse-xml': [2],
+      'no-jquery/no-prop': [2],
+      'no-jquery/no-proxy': [2],
+      'no-jquery/no-ready-shorthand': [2],
+      'no-jquery/no-ready': [2],
+      'no-jquery/no-selector-prop': [2],
+      'no-jquery/no-serialize': [2],
+      'no-jquery/no-size': [2],
+      'no-jquery/no-sizzle': [0],
+      'no-jquery/no-slide': [2],
+      'no-jquery/no-sub': [2],
+      'no-jquery/no-support': [2],
+      'no-jquery/no-text': [0],
+      'no-jquery/no-trigger': [0],
+      'no-jquery/no-trim': [2],
+      'no-jquery/no-type': [2],
+      'no-jquery/no-unique': [2],
+      'no-jquery/no-unload-shorthand': [2],
+      'no-jquery/no-val': [0],
+      'no-jquery/no-visibility': [2],
+      'no-jquery/no-when': [2],
+      'no-jquery/no-wrap': [2],
+      'no-jquery/variable-pattern': [2],
+      'no-label-var': [2],
+      'no-labels': [0],
+      'no-lone-blocks': [2],
+      'no-lonely-if': [0],
+      'no-loop-func': [0],
+      'no-loss-of-precision': [2],
+      'no-magic-numbers': [0],
+      'no-misleading-character-class': [2],
+      'no-multi-assign': [0],
+      'no-multi-str': [2],
+      'no-negated-condition': [0],
+      'no-nested-ternary': [0],
+      'no-new-func': [2],
+      'no-new-native-nonconstructor': [2],
+      'no-new-object': [2],
+      'no-new-symbol': [2],
+      'no-new-wrappers': [2],
+      'no-new': [0],
+      'no-nonoctal-decimal-escape': [2],
+      'no-obj-calls': [2],
+      'no-octal-escape': [2],
+      'no-octal': [2],
+      'no-param-reassign': [0],
+      'no-plusplus': [0],
+      'no-promise-executor-return': [0],
+      'no-proto': [2],
+      'no-prototype-builtins': [2],
+      'no-redeclare': [2],
+      'no-regex-spaces': [2],
+      'no-restricted-exports': [0],
+
+      'no-restricted-globals': [
+        2,
+        'addEventListener',
+        'blur',
+        'close',
+        'closed',
+        'confirm',
+        'defaultStatus',
+        'defaultstatus',
+        'error',
+        'event',
+        'external',
+        'find',
+        'focus',
+        'frameElement',
+        'frames',
+        'history',
+        'innerHeight',
+        'innerWidth',
+        'isFinite',
+        'isNaN',
+        'length',
+        'location',
+        'locationbar',
+        'menubar',
+        'moveBy',
+        'moveTo',
+        'name',
+        'onblur',
+        'onerror',
+        'onfocus',
+        'onload',
+        'onresize',
+        'onunload',
+        'open',
+        'opener',
+        'opera',
+        'outerHeight',
+        'outerWidth',
+        'pageXOffset',
+        'pageYOffset',
+        'parent',
+        'print',
+        'removeEventListener',
+        'resizeBy',
+        'resizeTo',
+        'screen',
+        'screenLeft',
+        'screenTop',
+        'screenX',
+        'screenY',
+        'scroll',
+        'scrollbars',
+        'scrollBy',
+        'scrollTo',
+        'scrollX',
+        'scrollY',
+        'self',
+        'status',
+        'statusbar',
+        'stop',
+        'toolbar',
+        'top',
+        '__dirname',
+        '__filename',
+      ],
+
+      'no-restricted-imports': [0],
+
+      'no-restricted-syntax': [
+        2,
+        'WithStatement',
+        'ForInStatement',
+        'LabeledStatement',
+        'SequenceExpression',
+        {
+          selector: "CallExpression[callee.name='fetch']",
+          message: 'use modules/fetch.js instead',
+        },
+      ],
+
+      'no-return-assign': [0],
+      'no-script-url': [2],
+
+      'no-self-assign': [2, {
+        props: true,
+      }],
+
+      'no-self-compare': [2],
+      'no-sequences': [2],
+      'no-setter-return': [2],
+      'no-shadow-restricted-names': [2],
+      'no-shadow': [0],
+      'no-sparse-arrays': [2],
+      'no-template-curly-in-string': [2],
+      'no-ternary': [0],
+      'no-this-before-super': [2],
+      'no-throw-literal': [2],
+      'no-undef-init': [2],
+
+      'no-undef': [2, {
+        typeof: true,
+      }],
+
+      'no-undefined': [0],
+      'no-underscore-dangle': [0],
+      'no-unexpected-multiline': [2],
+      'no-unmodified-loop-condition': [2],
+      'no-unneeded-ternary': [2],
+      'no-unreachable-loop': [2],
+      'no-unreachable': [2],
+      'no-unsafe-finally': [2],
+      'no-unsafe-negation': [2],
+      'no-unused-expressions': [2],
+      'no-unused-labels': [2],
+      'no-unused-private-class-members': [2],
+
+      'no-unused-vars': [2, {
+        args: 'all',
+        argsIgnorePattern: '^_',
+        varsIgnorePattern: '^_',
+        caughtErrorsIgnorePattern: '^_',
+        destructuredArrayIgnorePattern: '^_',
+        ignoreRestSiblings: false,
+      }],
+
+      'no-use-before-define': [2, {
+        functions: false,
+        classes: true,
+        variables: true,
+        allowNamedExports: true,
+      }],
+
+      'no-use-extend-native/no-use-extend-native': [2],
+      'no-useless-backreference': [2],
+      'no-useless-call': [2],
+      'no-useless-catch': [2],
+      'no-useless-computed-key': [2],
+      'no-useless-concat': [2],
+      'no-useless-constructor': [2],
+      'no-useless-escape': [2],
+      'no-useless-rename': [2],
+      'no-useless-return': [2],
+      'no-var': [2],
+      'no-void': [2],
+      'no-warning-comments': [0],
+      'no-with': [0],
+      'object-shorthand': [2, 'always'],
+      'one-var-declaration-per-line': [0],
+      'one-var': [0],
+      'operator-assignment': [2, 'always'],
+      'operator-linebreak': [2, 'after'],
+
+      'prefer-arrow-callback': [2, {
+        allowNamedFunctions: true,
+        allowUnboundThis: true,
+      }],
+
+      'prefer-const': [2, {
+        destructuring: 'all',
+        ignoreReadBeforeAssign: true,
+      }],
+
+      'prefer-destructuring': [0],
+      'prefer-exponentiation-operator': [2],
+      'prefer-named-capture-group': [0],
+      'prefer-numeric-literals': [2],
+      'prefer-object-has-own': [2],
+      'prefer-object-spread': [2],
+
+      'prefer-promise-reject-errors': [2, {
+        allowEmptyReject: false,
+      }],
+
+      'prefer-regex-literals': [2],
+      'prefer-rest-params': [2],
+      'prefer-spread': [2],
+      'prefer-template': [2],
+      radix: [2, 'as-needed'],
+      'regexp/confusing-quantifier': [2],
+      'regexp/control-character-escape': [2],
+      'regexp/hexadecimal-escape': [0],
+      'regexp/letter-case': [0],
+      'regexp/match-any': [2],
+      'regexp/negation': [2],
+      'regexp/no-contradiction-with-assertion': [0],
+      'regexp/no-control-character': [0],
+      'regexp/no-dupe-characters-character-class': [2],
+      'regexp/no-dupe-disjunctions': [2],
+      'regexp/no-empty-alternative': [2],
+      'regexp/no-empty-capturing-group': [2],
+      'regexp/no-empty-character-class': [0],
+      'regexp/no-empty-group': [2],
+      'regexp/no-empty-lookarounds-assertion': [2],
+      'regexp/no-empty-string-literal': [2],
+      'regexp/no-escape-backspace': [2],
+      'regexp/no-extra-lookaround-assertions': [0],
+      'regexp/no-invalid-regexp': [2],
+      'regexp/no-invisible-character': [2],
+      'regexp/no-lazy-ends': [2],
+      'regexp/no-legacy-features': [2],
+      'regexp/no-misleading-capturing-group': [0],
+      'regexp/no-misleading-unicode-character': [0],
+      'regexp/no-missing-g-flag': [2],
+      'regexp/no-non-standard-flag': [2],
+      'regexp/no-obscure-range': [2],
+      'regexp/no-octal': [2],
+      'regexp/no-optional-assertion': [2],
+      'regexp/no-potentially-useless-backreference': [2],
+      'regexp/no-standalone-backslash': [2],
+      'regexp/no-super-linear-backtracking': [0],
+      'regexp/no-super-linear-move': [0],
+      'regexp/no-trivially-nested-assertion': [2],
+      'regexp/no-trivially-nested-quantifier': [2],
+      'regexp/no-unused-capturing-group': [0],
+      'regexp/no-useless-assertions': [2],
+      'regexp/no-useless-backreference': [2],
+      'regexp/no-useless-character-class': [2],
+      'regexp/no-useless-dollar-replacements': [2],
+      'regexp/no-useless-escape': [2],
+      'regexp/no-useless-flag': [2],
+      'regexp/no-useless-lazy': [2],
+      'regexp/no-useless-non-capturing-group': [2],
+      'regexp/no-useless-quantifier': [2],
+      'regexp/no-useless-range': [2],
+      'regexp/no-useless-set-operand': [2],
+      'regexp/no-useless-string-literal': [2],
+      'regexp/no-useless-two-nums-quantifier': [2],
+      'regexp/no-zero-quantifier': [2],
+      'regexp/optimal-lookaround-quantifier': [2],
+      'regexp/optimal-quantifier-concatenation': [0],
+      'regexp/prefer-character-class': [0],
+      'regexp/prefer-d': [0],
+      'regexp/prefer-escape-replacement-dollar-char': [0],
+      'regexp/prefer-lookaround': [0],
+      'regexp/prefer-named-backreference': [0],
+      'regexp/prefer-named-capture-group': [0],
+      'regexp/prefer-named-replacement': [0],
+      'regexp/prefer-plus-quantifier': [2],
+      'regexp/prefer-predefined-assertion': [2],
+      'regexp/prefer-quantifier': [0],
+      'regexp/prefer-question-quantifier': [2],
+      'regexp/prefer-range': [2],
+      'regexp/prefer-regexp-exec': [2],
+      'regexp/prefer-regexp-test': [2],
+      'regexp/prefer-result-array-groups': [0],
+      'regexp/prefer-set-operation': [2],
+      'regexp/prefer-star-quantifier': [2],
+      'regexp/prefer-unicode-codepoint-escapes': [2],
+      'regexp/prefer-w': [0],
+      'regexp/require-unicode-regexp': [0],
+      'regexp/simplify-set-operations': [2],
+      'regexp/sort-alternatives': [0],
+      'regexp/sort-character-class-elements': [0],
+      'regexp/sort-flags': [0],
+      'regexp/strict': [2],
+      'regexp/unicode-escape': [0],
+      'regexp/use-ignore-case': [0],
+      'require-atomic-updates': [0],
+      'require-await': [0],
+      'require-unicode-regexp': [0],
+      'require-yield': [2],
+      'sonarjs/cognitive-complexity': [0],
+      'sonarjs/elseif-without-else': [0],
+      'sonarjs/max-switch-cases': [0],
+      'sonarjs/no-all-duplicated-branches': [2],
+      'sonarjs/no-collapsible-if': [0],
+      'sonarjs/no-collection-size-mischeck': [2],
+      'sonarjs/no-duplicate-string': [0],
+      'sonarjs/no-duplicated-branches': [0],
+      'sonarjs/no-element-overwrite': [2],
+      'sonarjs/no-empty-collection': [2],
+      'sonarjs/no-extra-arguments': [2],
+      'sonarjs/no-gratuitous-expressions': [2],
+      'sonarjs/no-identical-conditions': [2],
+      'sonarjs/no-identical-expressions': [2],
+      'sonarjs/no-identical-functions': [2, 5],
+      'sonarjs/no-ignored-return': [2],
+      'sonarjs/no-inverted-boolean-check': [2],
+      'sonarjs/no-nested-switch': [0],
+      'sonarjs/no-nested-template-literals': [0],
+      'sonarjs/no-one-iteration-loop': [2],
+      'sonarjs/no-redundant-boolean': [2],
+      'sonarjs/no-redundant-jump': [2],
+      'sonarjs/no-same-line-conditional': [2],
+      'sonarjs/no-small-switch': [0],
+      'sonarjs/no-unused-collection': [2],
+      'sonarjs/no-use-of-empty-return-value': [2],
+      'sonarjs/no-useless-catch': [2],
+      'sonarjs/non-existent-operator': [2],
+      'sonarjs/prefer-immediate-return': [0],
+      'sonarjs/prefer-object-literal': [0],
+      'sonarjs/prefer-single-boolean-return': [0],
+      'sonarjs/prefer-while': [2],
+      'sort-imports': [0],
+      'sort-keys': [0],
+      'sort-vars': [0],
+      strict: [0],
+      'symbol-description': [2],
+      'unicode-bom': [2, 'never'],
+      'unicorn/better-regex': [0],
+      'unicorn/catch-error-name': [0],
+      'unicorn/consistent-destructuring': [2],
+      'unicorn/consistent-empty-array-spread': [2],
+      'unicorn/consistent-existence-index-check': [2],
+      'unicorn/consistent-function-scoping': [2],
+      'unicorn/custom-error-definition': [0],
+      'unicorn/empty-brace-spaces': [2],
+      'unicorn/error-message': [0],
+      'unicorn/escape-case': [0],
+      'unicorn/expiring-todo-comments': [0],
+      'unicorn/explicit-length-check': [0],
+      'unicorn/filename-case': [0],
+      'unicorn/import-index': [0],
+      'unicorn/import-style': [0],
+      'unicorn/new-for-builtins': [2],
+      'unicorn/no-abusive-eslint-disable': [0],
+      'unicorn/no-anonymous-default-export': [0],
+      'unicorn/no-array-callback-reference': [0],
+      'unicorn/no-array-for-each': [2],
+      'unicorn/no-array-method-this-argument': [2],
+      'unicorn/no-array-push-push': [2],
+      'unicorn/no-array-reduce': [2],
+      'unicorn/no-await-expression-member': [0],
+      'unicorn/no-await-in-promise-methods': [2],
+      'unicorn/no-console-spaces': [0],
+      'unicorn/no-document-cookie': [2],
+      'unicorn/no-empty-file': [2],
+      'unicorn/no-for-loop': [0],
+      'unicorn/no-hex-escape': [0],
+      'unicorn/no-instanceof-array': [0],
+      'unicorn/no-invalid-fetch-options': [2],
+      'unicorn/no-invalid-remove-event-listener': [2],
+      'unicorn/no-keyword-prefix': [0],
+      'unicorn/no-length-as-slice-end': [2],
+      'unicorn/no-lonely-if': [2],
+      'unicorn/no-magic-array-flat-depth': [0],
+      'unicorn/no-negated-condition': [0],
+      'unicorn/no-negation-in-equality-check': [2],
+      'unicorn/no-nested-ternary': [0],
+      'unicorn/no-new-array': [0],
+      'unicorn/no-new-buffer': [0],
+      'unicorn/no-null': [0],
+      'unicorn/no-object-as-default-parameter': [0],
+      'unicorn/no-process-exit': [0],
+      'unicorn/no-single-promise-in-promise-methods': [2],
+      'unicorn/no-static-only-class': [2],
+      'unicorn/no-thenable': [2],
+      'unicorn/no-this-assignment': [2],
+      'unicorn/no-typeof-undefined': [2],
+      'unicorn/no-unnecessary-await': [2],
+      'unicorn/no-unnecessary-polyfills': [2],
+      'unicorn/no-unreadable-array-destructuring': [0],
+      'unicorn/no-unreadable-iife': [2],
+      'unicorn/no-unused-properties': [2],
+      'unicorn/no-useless-fallback-in-spread': [2],
+      'unicorn/no-useless-length-check': [2],
+      'unicorn/no-useless-promise-resolve-reject': [2],
+      'unicorn/no-useless-spread': [2],
+      'unicorn/no-useless-switch-case': [2],
+      'unicorn/no-useless-undefined': [0],
+      'unicorn/no-zero-fractions': [2],
+      'unicorn/number-literal-case': [0],
+      'unicorn/numeric-separators-style': [0],
+      'unicorn/prefer-add-event-listener': [2],
+      'unicorn/prefer-array-find': [2],
+      'unicorn/prefer-array-flat-map': [2],
+      'unicorn/prefer-array-flat': [2],
+      'unicorn/prefer-array-index-of': [2],
+      'unicorn/prefer-array-some': [2],
+      'unicorn/prefer-at': [0],
+      'unicorn/prefer-blob-reading-methods': [2],
+      'unicorn/prefer-code-point': [0],
+      'unicorn/prefer-date-now': [2],
+      'unicorn/prefer-default-parameters': [0],
+      'unicorn/prefer-dom-node-append': [2],
+      'unicorn/prefer-dom-node-dataset': [0],
+      'unicorn/prefer-dom-node-remove': [2],
+      'unicorn/prefer-dom-node-text-content': [2],
+      'unicorn/prefer-event-target': [2],
+      'unicorn/prefer-export-from': [0],
+      'unicorn/prefer-global-this': [0],
+      'unicorn/prefer-includes': [2],
+      'unicorn/prefer-json-parse-buffer': [0],
+      'unicorn/prefer-keyboard-event-key': [2],
+      'unicorn/prefer-logical-operator-over-ternary': [2],
+      'unicorn/prefer-math-min-max': [2],
+      'unicorn/prefer-math-trunc': [2],
+      'unicorn/prefer-modern-dom-apis': [0],
+      'unicorn/prefer-modern-math-apis': [2],
+      'unicorn/prefer-module': [2],
+      'unicorn/prefer-native-coercion-functions': [2],
+      'unicorn/prefer-negative-index': [2],
+      'unicorn/prefer-node-protocol': [2],
+      'unicorn/prefer-number-properties': [0],
+      'unicorn/prefer-object-from-entries': [2],
+      'unicorn/prefer-object-has-own': [0],
+      'unicorn/prefer-optional-catch-binding': [2],
+      'unicorn/prefer-prototype-methods': [0],
+      'unicorn/prefer-query-selector': [0],
+      'unicorn/prefer-reflect-apply': [0],
+      'unicorn/prefer-regexp-test': [2],
+      'unicorn/prefer-set-has': [0],
+      'unicorn/prefer-set-size': [2],
+      'unicorn/prefer-spread': [0],
+      'unicorn/prefer-string-raw': [0],
+      'unicorn/prefer-string-replace-all': [0],
+      'unicorn/prefer-string-slice': [0],
+      'unicorn/prefer-string-starts-ends-with': [2],
+      'unicorn/prefer-string-trim-start-end': [2],
+      'unicorn/prefer-structured-clone': [2],
+      'unicorn/prefer-switch': [0],
+      'unicorn/prefer-ternary': [0],
+      'unicorn/prefer-text-content': [2],
+      'unicorn/prefer-top-level-await': [0],
+      'unicorn/prefer-type-error': [0],
+      'unicorn/prevent-abbreviations': [0],
+      'unicorn/relative-url-style': [2],
+      'unicorn/require-array-join-separator': [2],
+      'unicorn/require-number-to-fixed-digits-argument': [2],
+      'unicorn/require-post-message-target-origin': [0],
+      'unicorn/string-content': [0],
+      'unicorn/switch-case-braces': [0],
+      'unicorn/template-indent': [2],
+      'unicorn/text-encoding-identifier-case': [0],
+      'unicorn/throw-new-error': [2],
+      'use-isnan': [2],
+
+      'valid-typeof': [2, {
+        requireStringLiterals: true,
+      }],
+
+      'vars-on-top': [0],
+      'wc/attach-shadow-constructor': [2],
+      'wc/define-tag-after-class-definition': [0],
+      'wc/expose-class-on-global': [0],
+      'wc/file-name-matches-element': [2],
+      'wc/guard-define-call': [0],
+      'wc/guard-super-call': [2],
+      'wc/max-elements-per-file': [0],
+      'wc/no-child-traversal-in-attributechangedcallback': [2],
+      'wc/no-child-traversal-in-connectedcallback': [2],
+      'wc/no-closed-shadow-root': [2],
+      'wc/no-constructor-attributes': [2],
+      'wc/no-constructor-params': [2],
+      'wc/no-constructor': [2],
+      'wc/no-customized-built-in-elements': [2],
+      'wc/no-exports-with-element': [0],
+      'wc/no-invalid-element-name': [2],
+      'wc/no-invalid-extends': [2],
+      'wc/no-method-prefixed-with-on': [2],
+      'wc/no-self-class': [2],
+      'wc/no-typos': [2],
+      'wc/require-listener-teardown': [2],
+      'wc/tag-name-matches-class': [2],
+      yoda: [2, 'never'],
+    },
+  },
+  {
+    ignores: ['*.vue', '**/*.vue'],
+    rules: {
+      'import-x/consistent-type-specifier-style': [0],
+      'import-x/default': [0],
+      'import-x/dynamic-import-chunkname': [0],
+      'import-x/export': [2],
+      'import-x/exports-last': [0],
+
+      'import-x/extensions': [2, 'always', {
+        ignorePackages: true,
+      }],
+
+      'import-x/first': [2],
+      'import-x/group-exports': [0],
+      'import-x/max-dependencies': [0],
+      'import-x/named': [2],
+      'import-x/namespace': [0],
+      'import-x/newline-after-import': [0],
+      'import-x/no-absolute-path': [0],
+      'import-x/no-amd': [2],
+      'import-x/no-anonymous-default-export': [0],
+      'import-x/no-commonjs': [2],
+
+      'import-x/no-cycle': [2, {
+        ignoreExternal: true,
+        maxDepth: 1,
+      }],
+
+      'import-x/no-default-export': [0],
+      'import-x/no-deprecated': [0],
+      'import-x/no-dynamic-require': [0],
+      'import-x/no-empty-named-blocks': [2],
+      'import-x/no-extraneous-dependencies': [2],
+      'import-x/no-import-module-exports': [0],
+      'import-x/no-internal-modules': [0],
+      'import-x/no-mutable-exports': [0],
+      'import-x/no-named-as-default-member': [0],
+      'import-x/no-named-as-default': [2],
+      'import-x/no-named-default': [0],
+      'import-x/no-named-export': [0],
+      'import-x/no-namespace': [0],
+      'import-x/no-nodejs-modules': [0],
+      'import-x/no-relative-packages': [0],
+      'import-x/no-relative-parent-imports': [0],
+      'import-x/no-restricted-paths': [0],
+      'import-x/no-self-import': [2],
+      'import-x/no-unassigned-import': [0],
+
+      'import-x/no-unresolved': [2, {
+        commonjs: true,
+        ignore: ['\\?.+$', '^vitest/'],
+      }],
+
+      'import-x/no-useless-path-segments': [2, {
+        commonjs: true,
+      }],
+
+      'import-x/no-webpack-loader-syntax': [2],
+      'import-x/order': [0],
+      'import-x/prefer-default-export': [0],
+      'import-x/unambiguous': [0],
+    },
+  },
+  {
+    files: ['web_src/**/*'],
+    languageOptions: {
+      globals: {
+        __webpack_public_path__: true,
+        process: false,
+      },
+    },
+  }, {
+    files: ['web_src/**/*', 'docs/**/*'],
+
+    languageOptions: {
+      globals: {
+        ...globals.browser,
+      },
+    },
+  }, {
+    files: ['web_src/**/*worker.*'],
+
+    languageOptions: {
+      globals: {
+        ...globals.worker,
+      },
+    },
+
+    rules: {
+      'no-restricted-globals': [
+        2,
+        'addEventListener',
+        'blur',
+        'close',
+        'closed',
+        'confirm',
+        'defaultStatus',
+        'defaultstatus',
+        'error',
+        'event',
+        'external',
+        'find',
+        'focus',
+        'frameElement',
+        'frames',
+        'history',
+        'innerHeight',
+        'innerWidth',
+        'isFinite',
+        'isNaN',
+        'length',
+        'locationbar',
+        'menubar',
+        'moveBy',
+        'moveTo',
+        'name',
+        'onblur',
+        'onerror',
+        'onfocus',
+        'onload',
+        'onresize',
+        'onunload',
+        'open',
+        'opener',
+        'opera',
+        'outerHeight',
+        'outerWidth',
+        'pageXOffset',
+        'pageYOffset',
+        'parent',
+        'print',
+        'removeEventListener',
+        'resizeBy',
+        'resizeTo',
+        'screen',
+        'screenLeft',
+        'screenTop',
+        'screenX',
+        'screenY',
+        'scroll',
+        'scrollbars',
+        'scrollBy',
+        'scrollTo',
+        'scrollX',
+        'scrollY',
+        'status',
+        'statusbar',
+        'stop',
+        'toolbar',
+        'top',
+      ],
+    },
+  }, {
+    files: ['**/*.config.*'],
+    languageOptions: {
       ecmaVersion: 'latest',
     },
-
-    ecmaVersion: 'latest',
-    sourceType: 'module',
-  },
-  rules: {
-    '@eslint-community/eslint-comments/disable-enable-pair': [2],
-    '@eslint-community/eslint-comments/no-aggregating-enable': [2],
-    '@eslint-community/eslint-comments/no-duplicate-disable': [2],
-    '@eslint-community/eslint-comments/no-restricted-disable': [0],
-    '@eslint-community/eslint-comments/no-unlimited-disable': [2],
-    '@eslint-community/eslint-comments/no-unused-disable': [2],
-    '@eslint-community/eslint-comments/no-unused-enable': [2],
-    '@eslint-community/eslint-comments/no-use': [0],
-    '@eslint-community/eslint-comments/require-description': [0],
-    '@stylistic/js/array-bracket-newline': [0],
-    '@stylistic/js/array-bracket-spacing': [2, 'never'],
-    '@stylistic/js/array-element-newline': [0],
-    '@stylistic/js/arrow-parens': [2, 'always'],
-
-    '@stylistic/js/arrow-spacing': [2, {
-      before: true,
-      after: true,
-    }],
-
-    '@stylistic/js/block-spacing': [0],
-
-    '@stylistic/js/brace-style': [2, '1tbs', {
-      allowSingleLine: true,
-    }],
-
-    '@stylistic/js/comma-dangle': [2, 'always-multiline'],
-
-    '@stylistic/js/comma-spacing': [2, {
-      before: false,
-      after: true,
-    }],
-
-    '@stylistic/js/comma-style': [2, 'last'],
-    '@stylistic/js/computed-property-spacing': [2, 'never'],
-    '@stylistic/js/dot-location': [2, 'property'],
-    '@stylistic/js/eol-last': [2],
-    '@stylistic/js/function-call-spacing': [2, 'never'],
-    '@stylistic/js/function-call-argument-newline': [0],
-    '@stylistic/js/function-paren-newline': [0],
-    '@stylistic/js/generator-star-spacing': [0],
-    '@stylistic/js/implicit-arrow-linebreak': [0],
-
-    '@stylistic/js/indent': [2, 2, {
-      ignoreComments: true,
-      SwitchCase: 1,
-    }],
-
-    '@stylistic/js/key-spacing': [2],
-    '@stylistic/js/keyword-spacing': [2],
-    '@stylistic/js/linebreak-style': [2, 'unix'],
-    '@stylistic/js/lines-around-comment': [0],
-    '@stylistic/js/lines-between-class-members': [0],
-    '@stylistic/js/max-len': [0],
-    '@stylistic/js/max-statements-per-line': [0],
-    '@stylistic/js/multiline-ternary': [0],
-    '@stylistic/js/new-parens': [2],
-    '@stylistic/js/newline-per-chained-call': [0],
-    '@stylistic/js/no-confusing-arrow': [0],
-    '@stylistic/js/no-extra-parens': [0],
-    '@stylistic/js/no-extra-semi': [2],
-    '@stylistic/js/no-floating-decimal': [0],
-    '@stylistic/js/no-mixed-operators': [0],
-    '@stylistic/js/no-mixed-spaces-and-tabs': [2],
-
-    '@stylistic/js/no-multi-spaces': [2, {
-      ignoreEOLComments: true,
-
-      exceptions: {
-        Property: true,
+    rules: {
+      'import-x/no-unused-modules': [0],
+      'import-x/no-unresolved': [0],
+      'import-x/no-named-as-default': [0],
+    },
+  }, {
+    files: ['**/*.test.*', 'web_src/js/test/setup.js'],
+    languageOptions: {
+      globals: {
+        ...vitestGlobals.environments.env.globals,
       },
-    }],
+    },
 
-    '@stylistic/js/no-multiple-empty-lines': [2, {
-      max: 1,
-      maxEOF: 0,
-      maxBOF: 0,
-    }],
+    rules: {
+      '@vitest/consistent-test-filename': [0],
+      '@vitest/consistent-test-it': [0],
+      '@vitest/expect-expect': [0],
+      '@vitest/max-expects': [0],
+      '@vitest/max-nested-describe': [0],
+      '@vitest/no-alias-methods': [0],
+      '@vitest/no-commented-out-tests': [0],
+      '@vitest/no-conditional-expect': [0],
+      '@vitest/no-conditional-in-test': [0],
+      '@vitest/no-conditional-tests': [0],
+      '@vitest/no-disabled-tests': [0],
+      '@vitest/no-done-callback': [0],
+      '@vitest/no-duplicate-hooks': [0],
+      '@vitest/no-focused-tests': [0],
+      '@vitest/no-hooks': [0],
+      '@vitest/no-identical-title': [2],
+      '@vitest/no-interpolation-in-snapshots': [0],
+      '@vitest/no-large-snapshots': [0],
+      '@vitest/no-mocks-import': [0],
+      '@vitest/no-restricted-matchers': [0],
+      '@vitest/no-restricted-vi-methods': [0],
+      '@vitest/no-standalone-expect': [0],
+      '@vitest/no-test-prefixes': [0],
+      '@vitest/no-test-return-statement': [0],
+      '@vitest/prefer-called-with': [0],
+      '@vitest/prefer-comparison-matcher': [0],
+      '@vitest/prefer-each': [0],
+      '@vitest/prefer-equality-matcher': [0],
+      '@vitest/prefer-expect-resolves': [0],
+      '@vitest/prefer-hooks-in-order': [0],
+      '@vitest/prefer-hooks-on-top': [2],
+      '@vitest/prefer-lowercase-title': [0],
+      '@vitest/prefer-mock-promise-shorthand': [0],
+      '@vitest/prefer-snapshot-hint': [0],
+      '@vitest/prefer-spy-on': [0],
+      '@vitest/prefer-strict-equal': [0],
+      '@vitest/prefer-to-be': [0],
+      '@vitest/prefer-to-be-falsy': [0],
+      '@vitest/prefer-to-be-object': [0],
+      '@vitest/prefer-to-be-truthy': [0],
+      '@vitest/prefer-to-contain': [0],
+      '@vitest/prefer-to-have-length': [0],
+      '@vitest/prefer-todo': [0],
+      '@vitest/require-hook': [0],
+      '@vitest/require-to-throw-message': [0],
+      '@vitest/require-top-level-describe': [0],
+      '@vitest/valid-describe-callback': [2],
+      '@vitest/valid-expect': [2],
+      '@vitest/valid-title': [2],
+    },
+  }, {
+    files: ['web_src/js/modules/fetch.js', 'web_src/js/standalone/**/*'],
 
-    '@stylistic/js/no-tabs': [2],
-    '@stylistic/js/no-trailing-spaces': [2],
-    '@stylistic/js/no-whitespace-before-property': [2],
-    '@stylistic/js/nonblock-statement-body-position': [2],
-    '@stylistic/js/object-curly-newline': [0],
-    '@stylistic/js/object-curly-spacing': [2, 'never'],
-    '@stylistic/js/object-property-newline': [0],
-    '@stylistic/js/one-var-declaration-per-line': [0],
-    '@stylistic/js/operator-linebreak': [2, 'after'],
-    '@stylistic/js/padded-blocks': [2, 'never'],
-    '@stylistic/js/padding-line-between-statements': [0],
-    '@stylistic/js/quote-props': [0],
-
-    '@stylistic/js/quotes': [2, 'single', {
-      avoidEscape: true,
-      allowTemplateLiterals: true,
-    }],
-
-    '@stylistic/js/rest-spread-spacing': [2, 'never'],
-
-    '@stylistic/js/semi': [2, 'always', {
-      omitLastInOneLineBlock: true,
-    }],
-
-    '@stylistic/js/semi-spacing': [2, {
-      before: false,
-      after: true,
-    }],
-
-    '@stylistic/js/semi-style': [2, 'last'],
-    '@stylistic/js/space-before-blocks': [2, 'always'],
-
-    '@stylistic/js/space-before-function-paren': [2, {
-      anonymous: 'ignore',
-      named: 'never',
-      asyncArrow: 'always',
-    }],
-
-    '@stylistic/js/space-in-parens': [2, 'never'],
-    '@stylistic/js/space-infix-ops': [2],
-    '@stylistic/js/space-unary-ops': [2],
-    '@stylistic/js/spaced-comment': [2, 'always'],
-    '@stylistic/js/switch-colon-spacing': [2],
-    '@stylistic/js/template-curly-spacing': [2, 'never'],
-    '@stylistic/js/template-tag-spacing': [2, 'never'],
-    '@stylistic/js/wrap-iife': [2, 'inside'],
-    '@stylistic/js/wrap-regex': [0],
-    '@stylistic/js/yield-star-spacing': [2, 'after'],
-    'accessor-pairs': [2],
-
-    'array-callback-return': [2, {
-      checkForEach: true,
-    }],
-
-    'array-func/avoid-reverse': [2],
-    'array-func/from-map': [2],
-    'array-func/no-unnecessary-this-arg': [2],
-    'array-func/prefer-array-from': [2],
-    'array-func/prefer-flat-map': [0],
-    'array-func/prefer-flat': [0],
-    'arrow-body-style': [0],
-    'block-scoped-var': [2],
-    camelcase: [0],
-    'capitalized-comments': [0],
-    'class-methods-use-this': [0],
-    complexity: [0],
-    'consistent-return': [0],
-    'consistent-this': [0],
-    'constructor-super': [2],
-    curly: [0],
-    'default-case-last': [2],
-    'default-case': [0],
-    'default-param-last': [0],
-    'dot-notation': [0],
-    eqeqeq: [2],
-    'for-direction': [2],
-    'func-name-matching': [2],
-    'func-names': [0],
-    'func-style': [0],
-    'getter-return': [2],
-    'grouped-accessor-pairs': [2],
-    'guard-for-in': [0],
-    'id-blacklist': [0],
-    'id-length': [0],
-    'id-match': [0],
-    'init-declarations': [0],
-    'line-comment-position': [0],
-    'logical-assignment-operators': [0],
-    'max-classes-per-file': [0],
-    'max-depth': [0],
-    'max-lines-per-function': [0],
-    'max-lines': [0],
-    'max-nested-callbacks': [0],
-    'max-params': [0],
-    'max-statements': [0],
-    'multiline-comment-style': [2, 'separate-lines'],
-    'new-cap': [0],
-    'no-alert': [0],
-    'no-array-constructor': [2],
-    'no-async-promise-executor': [0],
-    'no-await-in-loop': [0],
-    'no-bitwise': [0],
-    'no-buffer-constructor': [0],
-    'no-caller': [2],
-    'no-case-declarations': [2],
-    'no-class-assign': [2],
-    'no-compare-neg-zero': [2],
-    'no-cond-assign': [2, 'except-parens'],
-
-    'no-console': [1, {
-      allow: ['debug', 'info', 'warn', 'error'],
-    }],
-
-    'no-const-assign': [2],
-    'no-constant-binary-expression': [2],
-    'no-constant-condition': [0],
-    'no-constructor-return': [2],
-    'no-continue': [0],
-    'no-control-regex': [0],
-    'no-debugger': [1],
-    'no-delete-var': [2],
-    'no-div-regex': [0],
-    'no-dupe-args': [2],
-    'no-dupe-class-members': [2],
-    'no-dupe-else-if': [2],
-    'no-dupe-keys': [2],
-    'no-duplicate-case': [2],
-    'no-duplicate-imports': [2],
-    'no-else-return': [2],
-    'no-empty-character-class': [2],
-    'no-empty-function': [0],
-    'no-empty-pattern': [2],
-    'no-empty-static-block': [2],
-
-    'no-empty': [2, {
-      allowEmptyCatch: true,
-    }],
-
-    'no-eq-null': [2],
-    'no-eval': [2],
-    'no-ex-assign': [2],
-    'no-extend-native': [2],
-    'no-extra-bind': [2],
-    'no-extra-boolean-cast': [2],
-    'no-extra-label': [0],
-    'no-fallthrough': [2],
-    'no-func-assign': [2],
-    'no-global-assign': [2],
-    'no-implicit-coercion': [2],
-    'no-implicit-globals': [0],
-    'no-implied-eval': [2],
-    'no-import-assign': [2],
-    'no-inline-comments': [0],
-    'no-inner-declarations': [2],
-    'no-invalid-regexp': [2],
-    'no-invalid-this': [0],
-    'no-irregular-whitespace': [2],
-    'no-iterator': [2],
-    'no-jquery/no-ajax-events': [2],
-    'no-jquery/no-ajax': [2],
-    'no-jquery/no-and-self': [2],
-    'no-jquery/no-animate-toggle': [2],
-    'no-jquery/no-animate': [2],
-    'no-jquery/no-append-html': [2],
-    'no-jquery/no-attr': [2],
-    'no-jquery/no-bind': [2],
-    'no-jquery/no-box-model': [2],
-    'no-jquery/no-browser': [2],
-    'no-jquery/no-camel-case': [2],
-    'no-jquery/no-class-state': [2],
-    'no-jquery/no-class': [0],
-    'no-jquery/no-clone': [2],
-    'no-jquery/no-closest': [0],
-    'no-jquery/no-constructor-attributes': [2],
-    'no-jquery/no-contains': [2],
-    'no-jquery/no-context-prop': [2],
-    'no-jquery/no-css': [2],
-    'no-jquery/no-data': [0],
-    'no-jquery/no-deferred': [2],
-    'no-jquery/no-delegate': [2],
-    'no-jquery/no-each-collection': [0],
-    'no-jquery/no-each-util': [0],
-    'no-jquery/no-each': [0],
-    'no-jquery/no-error-shorthand': [2],
-    'no-jquery/no-error': [2],
-    'no-jquery/no-escape-selector': [2],
-    'no-jquery/no-event-shorthand': [2],
-    'no-jquery/no-extend': [2],
-    'no-jquery/no-fade': [2],
-    'no-jquery/no-filter': [0],
-    'no-jquery/no-find-collection': [0],
-    'no-jquery/no-find-util': [2],
-    'no-jquery/no-find': [0],
-    'no-jquery/no-fx-interval': [2],
-    'no-jquery/no-global-eval': [2],
-    'no-jquery/no-global-selector': [0],
-    'no-jquery/no-grep': [2],
-    'no-jquery/no-has': [2],
-    'no-jquery/no-hold-ready': [2],
-    'no-jquery/no-html': [0],
-    'no-jquery/no-in-array': [2],
-    'no-jquery/no-is-array': [2],
-    'no-jquery/no-is-empty-object': [2],
-    'no-jquery/no-is-function': [2],
-    'no-jquery/no-is-numeric': [2],
-    'no-jquery/no-is-plain-object': [2],
-    'no-jquery/no-is-window': [2],
-    'no-jquery/no-is': [2],
-    'no-jquery/no-jquery-constructor': [0],
-    'no-jquery/no-live': [2],
-    'no-jquery/no-load-shorthand': [2],
-    'no-jquery/no-load': [2],
-    'no-jquery/no-map-collection': [0],
-    'no-jquery/no-map-util': [2],
-    'no-jquery/no-map': [2],
-    'no-jquery/no-merge': [2],
-    'no-jquery/no-node-name': [2],
-    'no-jquery/no-noop': [2],
-    'no-jquery/no-now': [2],
-    'no-jquery/no-on-ready': [2],
-    'no-jquery/no-other-methods': [0],
-    'no-jquery/no-other-utils': [2],
-    'no-jquery/no-param': [2],
-    'no-jquery/no-parent': [0],
-    'no-jquery/no-parents': [2],
-    'no-jquery/no-parse-html-literal': [2],
-    'no-jquery/no-parse-html': [2],
-    'no-jquery/no-parse-json': [2],
-    'no-jquery/no-parse-xml': [2],
-    'no-jquery/no-prop': [2],
-    'no-jquery/no-proxy': [2],
-    'no-jquery/no-ready-shorthand': [2],
-    'no-jquery/no-ready': [2],
-    'no-jquery/no-selector-prop': [2],
-    'no-jquery/no-serialize': [2],
-    'no-jquery/no-size': [2],
-    'no-jquery/no-sizzle': [0],
-    'no-jquery/no-slide': [2],
-    'no-jquery/no-sub': [2],
-    'no-jquery/no-support': [2],
-    'no-jquery/no-text': [0],
-    'no-jquery/no-trigger': [0],
-    'no-jquery/no-trim': [2],
-    'no-jquery/no-type': [2],
-    'no-jquery/no-unique': [2],
-    'no-jquery/no-unload-shorthand': [2],
-    'no-jquery/no-val': [0],
-    'no-jquery/no-visibility': [2],
-    'no-jquery/no-when': [2],
-    'no-jquery/no-wrap': [2],
-    'no-jquery/variable-pattern': [2],
-    'no-label-var': [2],
-    'no-labels': [0],
-    'no-lone-blocks': [2],
-    'no-lonely-if': [0],
-    'no-loop-func': [0],
-    'no-loss-of-precision': [2],
-    'no-magic-numbers': [0],
-    'no-misleading-character-class': [2],
-    'no-multi-assign': [0],
-    'no-multi-str': [2],
-    'no-negated-condition': [0],
-    'no-nested-ternary': [0],
-    'no-new-func': [2],
-    'no-new-native-nonconstructor': [2],
-    'no-new-object': [2],
-    'no-new-symbol': [2],
-    'no-new-wrappers': [2],
-    'no-new': [0],
-    'no-nonoctal-decimal-escape': [2],
-    'no-obj-calls': [2],
-    'no-octal-escape': [2],
-    'no-octal': [2],
-    'no-param-reassign': [0],
-    'no-plusplus': [0],
-    'no-promise-executor-return': [0],
-    'no-proto': [2],
-    'no-prototype-builtins': [2],
-    'no-redeclare': [2],
-    'no-regex-spaces': [2],
-    'no-restricted-exports': [0],
-
-    'no-restricted-globals': [
-      2,
-      'addEventListener',
-      'blur',
-      'close',
-      'closed',
-      'confirm',
-      'defaultStatus',
-      'defaultstatus',
-      'error',
-      'event',
-      'external',
-      'find',
-      'focus',
-      'frameElement',
-      'frames',
-      'history',
-      'innerHeight',
-      'innerWidth',
-      'isFinite',
-      'isNaN',
-      'length',
-      'location',
-      'locationbar',
-      'menubar',
-      'moveBy',
-      'moveTo',
-      'name',
-      'onblur',
-      'onerror',
-      'onfocus',
-      'onload',
-      'onresize',
-      'onunload',
-      'open',
-      'opener',
-      'opera',
-      'outerHeight',
-      'outerWidth',
-      'pageXOffset',
-      'pageYOffset',
-      'parent',
-      'print',
-      'removeEventListener',
-      'resizeBy',
-      'resizeTo',
-      'screen',
-      'screenLeft',
-      'screenTop',
-      'screenX',
-      'screenY',
-      'scroll',
-      'scrollbars',
-      'scrollBy',
-      'scrollTo',
-      'scrollX',
-      'scrollY',
-      'self',
-      'status',
-      'statusbar',
-      'stop',
-      'toolbar',
-      'top',
-      '__dirname',
-      '__filename',
-    ],
-
-    'no-restricted-imports': [0],
-
-    'no-restricted-syntax': [
-      2,
-      'WithStatement',
-      'ForInStatement',
-      'LabeledStatement',
-      'SequenceExpression',
-      {
-        selector: "CallExpression[callee.name='fetch']",
-        message: 'use modules/fetch.js instead',
+    rules: {
+      'no-restricted-syntax': [
+        2,
+        'WithStatement',
+        'ForInStatement',
+        'LabeledStatement',
+        'SequenceExpression',
+      ],
+    },
+  }, {
+    files: ['tests/e2e/**/*.js', 'tests/e2e/**/*.ts'],
+    languageOptions: {
+      globals: {
+        ...globals.browser,
       },
-    ],
 
-    'no-return-assign': [0],
-    'no-script-url': [2],
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+    },
+    rules: {
+      ...playwright.configs['flat/recommended'].rules,
+      'playwright/no-conditional-in-test': [0],
+      'playwright/no-conditional-expect': [0],
+      'playwright/no-networkidle': [0],
 
-    'no-self-assign': [2, {
-      props: true,
-    }],
+      'playwright/no-skipped-test': [
+        2,
+        {
+          allowConditional: true,
+        },
+      ],
+      'playwright/no-useless-await': [2],
 
-    'no-self-compare': [2],
-    'no-sequences': [2],
-    'no-setter-return': [2],
-    'no-shadow-restricted-names': [2],
-    'no-shadow': [0],
-    'no-sparse-arrays': [2],
-    'no-template-curly-in-string': [2],
-    'no-ternary': [0],
-    'no-this-before-super': [2],
-    'no-throw-literal': [2],
-    'no-undef-init': [2],
-
-    'no-undef': [2, {
-      typeof: true,
-    }],
-
-    'no-undefined': [0],
-    'no-underscore-dangle': [0],
-    'no-unexpected-multiline': [2],
-    'no-unmodified-loop-condition': [2],
-    'no-unneeded-ternary': [2],
-    'no-unreachable-loop': [2],
-    'no-unreachable': [2],
-    'no-unsafe-finally': [2],
-    'no-unsafe-negation': [2],
-    'no-unused-expressions': [2],
-    'no-unused-labels': [2],
-    'no-unused-private-class-members': [2],
-
-    'no-unused-vars': [2, {
-      args: 'all',
-      argsIgnorePattern: '^_',
-      varsIgnorePattern: '^_',
-      caughtErrorsIgnorePattern: '^_',
-      destructuredArrayIgnorePattern: '^_',
-      ignoreRestSiblings: false,
-    }],
-
-    'no-use-before-define': [2, {
-      functions: false,
-      classes: true,
-      variables: true,
-      allowNamedExports: true,
-    }],
-
-    'no-use-extend-native/no-use-extend-native': [2],
-    'no-useless-backreference': [2],
-    'no-useless-call': [2],
-    'no-useless-catch': [2],
-    'no-useless-computed-key': [2],
-    'no-useless-concat': [2],
-    'no-useless-constructor': [2],
-    'no-useless-escape': [2],
-    'no-useless-rename': [2],
-    'no-useless-return': [2],
-    'no-var': [2],
-    'no-void': [2],
-    'no-warning-comments': [0],
-    'no-with': [0],
-    'object-shorthand': [2, 'always'],
-    'one-var-declaration-per-line': [0],
-    'one-var': [0],
-    'operator-assignment': [2, 'always'],
-    'operator-linebreak': [2, 'after'],
-
-    'prefer-arrow-callback': [2, {
-      allowNamedFunctions: true,
-      allowUnboundThis: true,
-    }],
-
-    'prefer-const': [2, {
-      destructuring: 'all',
-      ignoreReadBeforeAssign: true,
-    }],
-
-    'prefer-destructuring': [0],
-    'prefer-exponentiation-operator': [2],
-    'prefer-named-capture-group': [0],
-    'prefer-numeric-literals': [2],
-    'prefer-object-has-own': [2],
-    'prefer-object-spread': [2],
-
-    'prefer-promise-reject-errors': [2, {
-      allowEmptyReject: false,
-    }],
-
-    'prefer-regex-literals': [2],
-    'prefer-rest-params': [2],
-    'prefer-spread': [2],
-    'prefer-template': [2],
-    radix: [2, 'as-needed'],
-    'regexp/confusing-quantifier': [2],
-    'regexp/control-character-escape': [2],
-    'regexp/hexadecimal-escape': [0],
-    'regexp/letter-case': [0],
-    'regexp/match-any': [2],
-    'regexp/negation': [2],
-    'regexp/no-contradiction-with-assertion': [0],
-    'regexp/no-control-character': [0],
-    'regexp/no-dupe-characters-character-class': [2],
-    'regexp/no-dupe-disjunctions': [2],
-    'regexp/no-empty-alternative': [2],
-    'regexp/no-empty-capturing-group': [2],
-    'regexp/no-empty-character-class': [0],
-    'regexp/no-empty-group': [2],
-    'regexp/no-empty-lookarounds-assertion': [2],
-    'regexp/no-empty-string-literal': [2],
-    'regexp/no-escape-backspace': [2],
-    'regexp/no-extra-lookaround-assertions': [0],
-    'regexp/no-invalid-regexp': [2],
-    'regexp/no-invisible-character': [2],
-    'regexp/no-lazy-ends': [2],
-    'regexp/no-legacy-features': [2],
-    'regexp/no-misleading-capturing-group': [0],
-    'regexp/no-misleading-unicode-character': [0],
-    'regexp/no-missing-g-flag': [2],
-    'regexp/no-non-standard-flag': [2],
-    'regexp/no-obscure-range': [2],
-    'regexp/no-octal': [2],
-    'regexp/no-optional-assertion': [2],
-    'regexp/no-potentially-useless-backreference': [2],
-    'regexp/no-standalone-backslash': [2],
-    'regexp/no-super-linear-backtracking': [0],
-    'regexp/no-super-linear-move': [0],
-    'regexp/no-trivially-nested-assertion': [2],
-    'regexp/no-trivially-nested-quantifier': [2],
-    'regexp/no-unused-capturing-group': [0],
-    'regexp/no-useless-assertions': [2],
-    'regexp/no-useless-backreference': [2],
-    'regexp/no-useless-character-class': [2],
-    'regexp/no-useless-dollar-replacements': [2],
-    'regexp/no-useless-escape': [2],
-    'regexp/no-useless-flag': [2],
-    'regexp/no-useless-lazy': [2],
-    'regexp/no-useless-non-capturing-group': [2],
-    'regexp/no-useless-quantifier': [2],
-    'regexp/no-useless-range': [2],
-    'regexp/no-useless-set-operand': [2],
-    'regexp/no-useless-string-literal': [2],
-    'regexp/no-useless-two-nums-quantifier': [2],
-    'regexp/no-zero-quantifier': [2],
-    'regexp/optimal-lookaround-quantifier': [2],
-    'regexp/optimal-quantifier-concatenation': [0],
-    'regexp/prefer-character-class': [0],
-    'regexp/prefer-d': [0],
-    'regexp/prefer-escape-replacement-dollar-char': [0],
-    'regexp/prefer-lookaround': [0],
-    'regexp/prefer-named-backreference': [0],
-    'regexp/prefer-named-capture-group': [0],
-    'regexp/prefer-named-replacement': [0],
-    'regexp/prefer-plus-quantifier': [2],
-    'regexp/prefer-predefined-assertion': [2],
-    'regexp/prefer-quantifier': [0],
-    'regexp/prefer-question-quantifier': [2],
-    'regexp/prefer-range': [2],
-    'regexp/prefer-regexp-exec': [2],
-    'regexp/prefer-regexp-test': [2],
-    'regexp/prefer-result-array-groups': [0],
-    'regexp/prefer-set-operation': [2],
-    'regexp/prefer-star-quantifier': [2],
-    'regexp/prefer-unicode-codepoint-escapes': [2],
-    'regexp/prefer-w': [0],
-    'regexp/require-unicode-regexp': [0],
-    'regexp/simplify-set-operations': [2],
-    'regexp/sort-alternatives': [0],
-    'regexp/sort-character-class-elements': [0],
-    'regexp/sort-flags': [0],
-    'regexp/strict': [2],
-    'regexp/unicode-escape': [0],
-    'regexp/use-ignore-case': [0],
-    'require-atomic-updates': [0],
-    'require-await': [0],
-    'require-unicode-regexp': [0],
-    'require-yield': [2],
-    'sonarjs/cognitive-complexity': [0],
-    'sonarjs/elseif-without-else': [0],
-    'sonarjs/max-switch-cases': [0],
-    'sonarjs/no-all-duplicated-branches': [2],
-    'sonarjs/no-collapsible-if': [0],
-    'sonarjs/no-collection-size-mischeck': [2],
-    'sonarjs/no-duplicate-string': [0],
-    'sonarjs/no-duplicated-branches': [0],
-    'sonarjs/no-element-overwrite': [2],
-    'sonarjs/no-empty-collection': [2],
-    'sonarjs/no-extra-arguments': [2],
-    'sonarjs/no-gratuitous-expressions': [2],
-    'sonarjs/no-identical-conditions': [2],
-    'sonarjs/no-identical-expressions': [2],
-    'sonarjs/no-identical-functions': [2, 5],
-    'sonarjs/no-ignored-return': [2],
-    'sonarjs/no-inverted-boolean-check': [2],
-    'sonarjs/no-nested-switch': [0],
-    'sonarjs/no-nested-template-literals': [0],
-    'sonarjs/no-one-iteration-loop': [2],
-    'sonarjs/no-redundant-boolean': [2],
-    'sonarjs/no-redundant-jump': [2],
-    'sonarjs/no-same-line-conditional': [2],
-    'sonarjs/no-small-switch': [0],
-    'sonarjs/no-unused-collection': [2],
-    'sonarjs/no-use-of-empty-return-value': [2],
-    'sonarjs/no-useless-catch': [2],
-    'sonarjs/non-existent-operator': [2],
-    'sonarjs/prefer-immediate-return': [0],
-    'sonarjs/prefer-object-literal': [0],
-    'sonarjs/prefer-single-boolean-return': [0],
-    'sonarjs/prefer-while': [2],
-    'sort-imports': [0],
-    'sort-keys': [0],
-    'sort-vars': [0],
-    strict: [0],
-    'symbol-description': [2],
-    'unicode-bom': [2, 'never'],
-    'unicorn/better-regex': [0],
-    'unicorn/catch-error-name': [0],
-    'unicorn/consistent-destructuring': [2],
-    'unicorn/consistent-empty-array-spread': [2],
-    'unicorn/consistent-existence-index-check': [2],
-    'unicorn/consistent-function-scoping': [2],
-    'unicorn/custom-error-definition': [0],
-    'unicorn/empty-brace-spaces': [2],
-    'unicorn/error-message': [0],
-    'unicorn/escape-case': [0],
-    'unicorn/expiring-todo-comments': [0],
-    'unicorn/explicit-length-check': [0],
-    'unicorn/filename-case': [0],
-    'unicorn/import-index': [0],
-    'unicorn/import-style': [0],
-    'unicorn/new-for-builtins': [2],
-    'unicorn/no-abusive-eslint-disable': [0],
-    'unicorn/no-anonymous-default-export': [0],
-    'unicorn/no-array-callback-reference': [0],
-    'unicorn/no-array-for-each': [2],
-    'unicorn/no-array-method-this-argument': [2],
-    'unicorn/no-array-push-push': [2],
-    'unicorn/no-array-reduce': [2],
-    'unicorn/no-await-expression-member': [0],
-    'unicorn/no-await-in-promise-methods': [2],
-    'unicorn/no-console-spaces': [0],
-    'unicorn/no-document-cookie': [2],
-    'unicorn/no-empty-file': [2],
-    'unicorn/no-for-loop': [0],
-    'unicorn/no-hex-escape': [0],
-    'unicorn/no-instanceof-array': [0],
-    'unicorn/no-invalid-fetch-options': [2],
-    'unicorn/no-invalid-remove-event-listener': [2],
-    'unicorn/no-keyword-prefix': [0],
-    'unicorn/no-length-as-slice-end': [2],
-    'unicorn/no-lonely-if': [2],
-    'unicorn/no-magic-array-flat-depth': [0],
-    'unicorn/no-negated-condition': [0],
-    'unicorn/no-negation-in-equality-check': [2],
-    'unicorn/no-nested-ternary': [0],
-    'unicorn/no-new-array': [0],
-    'unicorn/no-new-buffer': [0],
-    'unicorn/no-null': [0],
-    'unicorn/no-object-as-default-parameter': [0],
-    'unicorn/no-process-exit': [0],
-    'unicorn/no-single-promise-in-promise-methods': [2],
-    'unicorn/no-static-only-class': [2],
-    'unicorn/no-thenable': [2],
-    'unicorn/no-this-assignment': [2],
-    'unicorn/no-typeof-undefined': [2],
-    'unicorn/no-unnecessary-await': [2],
-    'unicorn/no-unnecessary-polyfills': [2],
-    'unicorn/no-unreadable-array-destructuring': [0],
-    'unicorn/no-unreadable-iife': [2],
-    'unicorn/no-unused-properties': [2],
-    'unicorn/no-useless-fallback-in-spread': [2],
-    'unicorn/no-useless-length-check': [2],
-    'unicorn/no-useless-promise-resolve-reject': [2],
-    'unicorn/no-useless-spread': [2],
-    'unicorn/no-useless-switch-case': [2],
-    'unicorn/no-useless-undefined': [0],
-    'unicorn/no-zero-fractions': [2],
-    'unicorn/number-literal-case': [0],
-    'unicorn/numeric-separators-style': [0],
-    'unicorn/prefer-add-event-listener': [2],
-    'unicorn/prefer-array-find': [2],
-    'unicorn/prefer-array-flat-map': [2],
-    'unicorn/prefer-array-flat': [2],
-    'unicorn/prefer-array-index-of': [2],
-    'unicorn/prefer-array-some': [2],
-    'unicorn/prefer-at': [0],
-    'unicorn/prefer-blob-reading-methods': [2],
-    'unicorn/prefer-code-point': [0],
-    'unicorn/prefer-date-now': [2],
-    'unicorn/prefer-default-parameters': [0],
-    'unicorn/prefer-dom-node-append': [2],
-    'unicorn/prefer-dom-node-dataset': [0],
-    'unicorn/prefer-dom-node-remove': [2],
-    'unicorn/prefer-dom-node-text-content': [2],
-    'unicorn/prefer-event-target': [2],
-    'unicorn/prefer-export-from': [0],
-    'unicorn/prefer-global-this': [0],
-    'unicorn/prefer-includes': [2],
-    'unicorn/prefer-json-parse-buffer': [0],
-    'unicorn/prefer-keyboard-event-key': [2],
-    'unicorn/prefer-logical-operator-over-ternary': [2],
-    'unicorn/prefer-math-min-max': [2],
-    'unicorn/prefer-math-trunc': [2],
-    'unicorn/prefer-modern-dom-apis': [0],
-    'unicorn/prefer-modern-math-apis': [2],
-    'unicorn/prefer-module': [2],
-    'unicorn/prefer-native-coercion-functions': [2],
-    'unicorn/prefer-negative-index': [2],
-    'unicorn/prefer-node-protocol': [2],
-    'unicorn/prefer-number-properties': [0],
-    'unicorn/prefer-object-from-entries': [2],
-    'unicorn/prefer-object-has-own': [0],
-    'unicorn/prefer-optional-catch-binding': [2],
-    'unicorn/prefer-prototype-methods': [0],
-    'unicorn/prefer-query-selector': [0],
-    'unicorn/prefer-reflect-apply': [0],
-    'unicorn/prefer-regexp-test': [2],
-    'unicorn/prefer-set-has': [0],
-    'unicorn/prefer-set-size': [2],
-    'unicorn/prefer-spread': [0],
-    'unicorn/prefer-string-raw': [0],
-    'unicorn/prefer-string-replace-all': [0],
-    'unicorn/prefer-string-slice': [0],
-    'unicorn/prefer-string-starts-ends-with': [2],
-    'unicorn/prefer-string-trim-start-end': [2],
-    'unicorn/prefer-structured-clone': [2],
-    'unicorn/prefer-switch': [0],
-    'unicorn/prefer-ternary': [0],
-    'unicorn/prefer-text-content': [2],
-    'unicorn/prefer-top-level-await': [0],
-    'unicorn/prefer-type-error': [0],
-    'unicorn/prevent-abbreviations': [0],
-    'unicorn/relative-url-style': [2],
-    'unicorn/require-array-join-separator': [2],
-    'unicorn/require-number-to-fixed-digits-argument': [2],
-    'unicorn/require-post-message-target-origin': [0],
-    'unicorn/string-content': [0],
-    'unicorn/switch-case-braces': [0],
-    'unicorn/template-indent': [2],
-    'unicorn/text-encoding-identifier-case': [0],
-    'unicorn/throw-new-error': [2],
-    'use-isnan': [2],
-
-    'valid-typeof': [2, {
-      requireStringLiterals: true,
-    }],
-
-    'vars-on-top': [0],
-    'wc/attach-shadow-constructor': [2],
-    'wc/define-tag-after-class-definition': [0],
-    'wc/expose-class-on-global': [0],
-    'wc/file-name-matches-element': [2],
-    'wc/guard-define-call': [0],
-    'wc/guard-super-call': [2],
-    'wc/max-elements-per-file': [0],
-    'wc/no-child-traversal-in-attributechangedcallback': [2],
-    'wc/no-child-traversal-in-connectedcallback': [2],
-    'wc/no-closed-shadow-root': [2],
-    'wc/no-constructor-attributes': [2],
-    'wc/no-constructor-params': [2],
-    'wc/no-constructor': [2],
-    'wc/no-customized-built-in-elements': [2],
-    'wc/no-exports-with-element': [0],
-    'wc/no-invalid-element-name': [2],
-    'wc/no-invalid-extends': [2],
-    'wc/no-method-prefixed-with-on': [2],
-    'wc/no-self-class': [2],
-    'wc/no-typos': [2],
-    'wc/require-listener-teardown': [2],
-    'wc/tag-name-matches-class': [2],
-    yoda: [2, 'never'],
-  },
-},
-{
-  ignores: ['*.vue', '**/*.vue'],
-  rules: {
-    'import-x/consistent-type-specifier-style': [0],
-    'import-x/default': [0],
-    'import-x/dynamic-import-chunkname': [0],
-    'import-x/export': [2],
-    'import-x/exports-last': [0],
-
-    'import-x/extensions': [2, 'always', {
-      ignorePackages: true,
-    }],
-
-    'import-x/first': [2],
-    'import-x/group-exports': [0],
-    'import-x/max-dependencies': [0],
-    'import-x/named': [2],
-    'import-x/namespace': [0],
-    'import-x/newline-after-import': [0],
-    'import-x/no-absolute-path': [0],
-    'import-x/no-amd': [2],
-    'import-x/no-anonymous-default-export': [0],
-    'import-x/no-commonjs': [2],
-
-    'import-x/no-cycle': [2, {
-      ignoreExternal: true,
-      maxDepth: 1,
-    }],
-
-    'import-x/no-default-export': [0],
-    'import-x/no-deprecated': [0],
-    'import-x/no-dynamic-require': [0],
-    'import-x/no-empty-named-blocks': [2],
-    'import-x/no-extraneous-dependencies': [2],
-    'import-x/no-import-module-exports': [0],
-    'import-x/no-internal-modules': [0],
-    'import-x/no-mutable-exports': [0],
-    'import-x/no-named-as-default-member': [0],
-    'import-x/no-named-as-default': [2],
-    'import-x/no-named-default': [0],
-    'import-x/no-named-export': [0],
-    'import-x/no-namespace': [0],
-    'import-x/no-nodejs-modules': [0],
-    'import-x/no-relative-packages': [0],
-    'import-x/no-relative-parent-imports': [0],
-    'import-x/no-restricted-paths': [0],
-    'import-x/no-self-import': [2],
-    'import-x/no-unassigned-import': [0],
-
-    'import-x/no-unresolved': [2, {
-      commonjs: true,
-      ignore: ['\\?.+$', '^vitest/'],
-    }],
-
-    'import-x/no-useless-path-segments': [2, {
-      commonjs: true,
-    }],
-
-    'import-x/no-webpack-loader-syntax': [2],
-    'import-x/order': [0],
-    'import-x/prefer-default-export': [0],
-    'import-x/unambiguous': [0],
-  },
-},
-{
-  files: ['web_src/**/*'],
-  languageOptions: {
-    globals: {
-      __webpack_public_path__: true,
-      process: false,
+      'playwright/prefer-comparison-matcher': [2],
+      'playwright/prefer-equality-matcher': [2],
+      'playwright/prefer-native-locators': [2],
+      'playwright/prefer-to-contain': [2],
+      'playwright/prefer-to-have-length': [2],
+      'playwright/require-to-throw-message': [2],
     },
   },
-}, {
-  files: ['web_src/**/*', 'docs/**/*'],
-
-  languageOptions: {
-    globals: {
-      ...globals.browser,
-    },
-  },
-}, {
-  files: ['web_src/**/*worker.*'],
-
-  languageOptions: {
-    globals: {
-      ...globals.worker,
-    },
-  },
-
-  rules: {
-    'no-restricted-globals': [
-      2,
-      'addEventListener',
-      'blur',
-      'close',
-      'closed',
-      'confirm',
-      'defaultStatus',
-      'defaultstatus',
-      'error',
-      'event',
-      'external',
-      'find',
-      'focus',
-      'frameElement',
-      'frames',
-      'history',
-      'innerHeight',
-      'innerWidth',
-      'isFinite',
-      'isNaN',
-      'length',
-      'locationbar',
-      'menubar',
-      'moveBy',
-      'moveTo',
-      'name',
-      'onblur',
-      'onerror',
-      'onfocus',
-      'onload',
-      'onresize',
-      'onunload',
-      'open',
-      'opener',
-      'opera',
-      'outerHeight',
-      'outerWidth',
-      'pageXOffset',
-      'pageYOffset',
-      'parent',
-      'print',
-      'removeEventListener',
-      'resizeBy',
-      'resizeTo',
-      'screen',
-      'screenLeft',
-      'screenTop',
-      'screenX',
-      'screenY',
-      'scroll',
-      'scrollbars',
-      'scrollBy',
-      'scrollTo',
-      'scrollX',
-      'scrollY',
-      'status',
-      'statusbar',
-      'stop',
-      'toolbar',
-      'top',
-    ],
-  },
-}, {
-  files: ['**/*.config.*'],
-  languageOptions: {
-    ecmaVersion: 'latest',
-  },
-  rules: {
-    'import-x/no-unused-modules': [0],
-    'import-x/no-unresolved': [0],
-  },
-}, {
-  files: ['**/*.test.*', 'web_src/js/test/setup.js'],
-  languageOptions: {
-    globals: {
-      ...vitestGlobals.environments.env.globals,
-    },
-  },
-
-  rules: {
-    '@vitest/consistent-test-filename': [0],
-    '@vitest/consistent-test-it': [0],
-    '@vitest/expect-expect': [0],
-    '@vitest/max-expects': [0],
-    '@vitest/max-nested-describe': [0],
-    '@vitest/no-alias-methods': [0],
-    '@vitest/no-commented-out-tests': [0],
-    '@vitest/no-conditional-expect': [0],
-    '@vitest/no-conditional-in-test': [0],
-    '@vitest/no-conditional-tests': [0],
-    '@vitest/no-disabled-tests': [0],
-    '@vitest/no-done-callback': [0],
-    '@vitest/no-duplicate-hooks': [0],
-    '@vitest/no-focused-tests': [0],
-    '@vitest/no-hooks': [0],
-    '@vitest/no-identical-title': [2],
-    '@vitest/no-interpolation-in-snapshots': [0],
-    '@vitest/no-large-snapshots': [0],
-    '@vitest/no-mocks-import': [0],
-    '@vitest/no-restricted-matchers': [0],
-    '@vitest/no-restricted-vi-methods': [0],
-    '@vitest/no-standalone-expect': [0],
-    '@vitest/no-test-prefixes': [0],
-    '@vitest/no-test-return-statement': [0],
-    '@vitest/prefer-called-with': [0],
-    '@vitest/prefer-comparison-matcher': [0],
-    '@vitest/prefer-each': [0],
-    '@vitest/prefer-equality-matcher': [0],
-    '@vitest/prefer-expect-resolves': [0],
-    '@vitest/prefer-hooks-in-order': [0],
-    '@vitest/prefer-hooks-on-top': [2],
-    '@vitest/prefer-lowercase-title': [0],
-    '@vitest/prefer-mock-promise-shorthand': [0],
-    '@vitest/prefer-snapshot-hint': [0],
-    '@vitest/prefer-spy-on': [0],
-    '@vitest/prefer-strict-equal': [0],
-    '@vitest/prefer-to-be': [0],
-    '@vitest/prefer-to-be-falsy': [0],
-    '@vitest/prefer-to-be-object': [0],
-    '@vitest/prefer-to-be-truthy': [0],
-    '@vitest/prefer-to-contain': [0],
-    '@vitest/prefer-to-have-length': [0],
-    '@vitest/prefer-todo': [0],
-    '@vitest/require-hook': [0],
-    '@vitest/require-to-throw-message': [0],
-    '@vitest/require-top-level-describe': [0],
-    '@vitest/valid-describe-callback': [2],
-    '@vitest/valid-expect': [2],
-    '@vitest/valid-title': [2],
-  },
-}, {
-  files: ['web_src/js/modules/fetch.js', 'web_src/js/standalone/**/*'],
-
-  rules: {
-    'no-restricted-syntax': [
-      2,
-      'WithStatement',
-      'ForInStatement',
-      'LabeledStatement',
-      'SequenceExpression',
-    ],
-  },
-}, {
-  files: ['tests/e2e/**/*.js'],
-  languageOptions: {
-    globals: {
-      ...globals.browser,
-    },
-
-    ecmaVersion: 'latest',
-    sourceType: 'module',
-  },
-  rules: {
-    ...playwright.configs['flat/recommended'].rules,
-    'playwright/no-conditional-in-test': [0],
-    'playwright/no-conditional-expect': [0],
-    'playwright/no-networkidle': [0],
-
-    'playwright/no-skipped-test': [
-      2,
-      {
-        allowConditional: true,
+  ...vue.configs['flat/recommended'],
+  {
+    files: ['web_src/js/components/*.vue'],
+    languageOptions: {
+      globals: {
+        ...globals.browser,
       },
-    ],
-    'playwright/no-useless-await': [2],
 
-    'playwright/prefer-comparison-matcher': [2],
-    'playwright/prefer-equality-matcher': [2],
-    'playwright/prefer-native-locators': [2],
-    'playwright/prefer-to-contain': [2],
-    'playwright/prefer-to-have-length': [2],
-    'playwright/require-to-throw-message': [2],
-  },
-},
-...vue.configs['flat/recommended'],
-{
-  files: ['web_src/js/components/*.vue'],
-  languageOptions: {
-    globals: {
-      ...globals.browser,
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+    },
+    rules: {
+      'vue/attributes-order': [0],
+      'vue/html-closing-bracket-spacing': [2, {
+        startTag: 'never',
+        endTag: 'never',
+        selfClosingTag: 'never',
+      }],
+      'vue/max-attributes-per-line': [0],
+      'vue-scoped-css/enforce-style-type': [0],
     },
-
-    ecmaVersion: 'latest',
-    sourceType: 'module',
   },
-  rules: {
-    'vue/attributes-order': [0],
-    'vue/html-closing-bracket-spacing': [2, {
-      startTag: 'never',
-      endTag: 'never',
-      selfClosingTag: 'never',
-    }],
-    'vue/max-attributes-per-line': [0],
-    'vue-scoped-css/enforce-style-type': [0],
-  },
-},
 
-];
+);
diff --git a/package-lock.json b/package-lock.json
index d08c9b7c7c..0206dc2ef8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -66,11 +66,13 @@
         "@stoplight/spectral-cli": "6.13.1",
         "@stylistic/eslint-plugin-js": "2.9.0",
         "@stylistic/stylelint-plugin": "3.1.1",
+        "@typescript-eslint/parser": "8.11.0",
         "@vitejs/plugin-vue": "5.1.4",
         "@vitest/coverage-v8": "2.1.4",
         "@vitest/eslint-plugin": "1.1.7",
         "@vue/test-utils": "2.4.6",
         "eslint": "9.13.0",
+        "eslint-import-resolver-typescript": "3.6.3",
         "eslint-plugin-array-func": "4.0.0",
         "eslint-plugin-import-x": "4.3.1",
         "eslint-plugin-no-jquery": "3.0.2",
@@ -93,6 +95,8 @@
         "stylelint-declaration-strict-value": "1.10.6",
         "stylelint-value-no-unknown-custom-properties": "6.0.1",
         "svgo": "3.2.0",
+        "typescript": "5.6.3",
+        "typescript-eslint": "8.11.0",
         "vite-string-plugin": "1.3.4",
         "vitest": "2.1.4"
       },
@@ -2485,262 +2489,6 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
-    "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
-      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
-      "cpu": [
-        "ppc64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "aix"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/android-arm": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
-      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
-      "cpu": [
-        "arm"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/android-arm64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
-      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/android-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
-      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
-      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/darwin-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
-      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
-      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
-      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-arm": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
-      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
-      "cpu": [
-        "arm"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-arm64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
-      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-ia32": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
-      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
-      "cpu": [
-        "ia32"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-loong64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
-      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
-      "cpu": [
-        "loong64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
-      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
-      "cpu": [
-        "mips64el"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
-      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
-      "cpu": [
-        "ppc64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
-      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
-      "cpu": [
-        "riscv64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-s390x": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
-      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
-      "cpu": [
-        "s390x"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/@esbuild/linux-x64": {
       "version": "0.21.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
@@ -2757,102 +2505,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
-      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
-      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/sunos-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
-      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/win32-arm64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
-      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
-      "cpu": [
-        "arm64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/win32-ia32": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
-      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
-      "cpu": [
-        "ia32"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/win32-x64": {
-      "version": "0.21.5",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
-      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/@eslint-community/eslint-plugin-eslint-comments": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.0.tgz",
@@ -3502,6 +3154,16 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@nolyfill/is-core-module": {
+      "version": "1.0.39",
+      "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+      "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.4.0"
+      }
+    },
     "node_modules/@npmcli/fs": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz",
@@ -3614,188 +3276,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz",
-      "integrity": "sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz",
-      "integrity": "sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz",
-      "integrity": "sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz",
-      "integrity": "sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-arm64": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz",
-      "integrity": "sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-x64": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz",
-      "integrity": "sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz",
-      "integrity": "sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz",
-      "integrity": "sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz",
-      "integrity": "sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz",
-      "integrity": "sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz",
-      "integrity": "sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz",
-      "integrity": "sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz",
-      "integrity": "sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
       "version": "4.24.2",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz",
@@ -3824,48 +3304,6 @@
         "linux"
       ]
     },
-    "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz",
-      "integrity": "sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz",
-      "integrity": "sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.24.2",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz",
-      "integrity": "sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
     "node_modules/@rtsao/scc": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -4551,6 +3989,69 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz",
+      "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "8.11.0",
+        "@typescript-eslint/type-utils": "8.11.0",
+        "@typescript-eslint/utils": "8.11.0",
+        "@typescript-eslint/visitor-keys": "8.11.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.3.1",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^1.3.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+        "eslint": "^8.57.0 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz",
+      "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "8.11.0",
+        "@typescript-eslint/types": "8.11.0",
+        "@typescript-eslint/typescript-estree": "8.11.0",
+        "@typescript-eslint/visitor-keys": "8.11.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@typescript-eslint/scope-manager": {
       "version": "8.11.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz",
@@ -4569,6 +4070,31 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz",
+      "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/typescript-estree": "8.11.0",
+        "@typescript-eslint/utils": "8.11.0",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^1.3.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@typescript-eslint/types": {
       "version": "8.11.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz",
@@ -7908,6 +7434,42 @@
         "ms": "^2.1.1"
       }
     },
+    "node_modules/eslint-import-resolver-typescript": {
+      "version": "3.6.3",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz",
+      "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "@nolyfill/is-core-module": "1.0.39",
+        "debug": "^4.3.5",
+        "enhanced-resolve": "^5.15.0",
+        "eslint-module-utils": "^2.8.1",
+        "fast-glob": "^3.3.2",
+        "get-tsconfig": "^4.7.5",
+        "is-bun-module": "^1.0.2",
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
+      },
+      "peerDependencies": {
+        "eslint": "*",
+        "eslint-plugin-import": "*",
+        "eslint-plugin-import-x": "*"
+      },
+      "peerDependenciesMeta": {
+        "eslint-plugin-import": {
+          "optional": true
+        },
+        "eslint-plugin-import-x": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/eslint-module-utils": {
       "version": "2.12.0",
       "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
@@ -9349,20 +8911,6 @@
       "dev": true,
       "license": "ISC"
     },
-    "node_modules/fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -10193,6 +9741,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-bun-module": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.2.1.tgz",
+      "integrity": "sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.6.3"
+      }
+    },
     "node_modules/is-callable": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -15298,6 +14856,30 @@
         "node": ">=14.17"
       }
     },
+    "node_modules/typescript-eslint": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.11.0.tgz",
+      "integrity": "sha512-cBRGnW3FSlxaYwU8KfAewxFK5uzeOAp0l2KebIlPDOT5olVi65KDG/yjBooPBG0kGW/HLkoz1c/iuBFehcS3IA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/eslint-plugin": "8.11.0",
+        "@typescript-eslint/parser": "8.11.0",
+        "@typescript-eslint/utils": "8.11.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/typo-js": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.4.tgz",
@@ -15597,21 +15179,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/vite/node_modules/fsevents": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
-      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
     "node_modules/vite/node_modules/rollup": {
       "version": "4.24.2",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz",
diff --git a/package.json b/package.json
index c420353260..f852fbe9cd 100644
--- a/package.json
+++ b/package.json
@@ -65,11 +65,13 @@
     "@stoplight/spectral-cli": "6.13.1",
     "@stylistic/eslint-plugin-js": "2.9.0",
     "@stylistic/stylelint-plugin": "3.1.1",
+    "@typescript-eslint/parser": "8.11.0",
     "@vitejs/plugin-vue": "5.1.4",
     "@vitest/coverage-v8": "2.1.4",
     "@vitest/eslint-plugin": "1.1.7",
     "@vue/test-utils": "2.4.6",
     "eslint": "9.13.0",
+    "eslint-import-resolver-typescript": "3.6.3",
     "eslint-plugin-array-func": "4.0.0",
     "eslint-plugin-import-x": "4.3.1",
     "eslint-plugin-no-jquery": "3.0.2",
@@ -92,8 +94,12 @@
     "stylelint-declaration-strict-value": "1.10.6",
     "stylelint-value-no-unknown-custom-properties": "6.0.1",
     "svgo": "3.2.0",
+    "typescript": "5.6.3",
+    "typescript-eslint": "8.11.0",
     "vite-string-plugin": "1.3.4",
     "vitest": "2.1.4"
   },
-  "browserslist": ["defaults"]
+  "browserslist": [
+    "defaults"
+  ]
 }
diff --git a/playwright.config.js b/playwright.config.ts
similarity index 99%
rename from playwright.config.js
rename to playwright.config.ts
index 25e2a7ab71..194f7f7d36 100644
--- a/playwright.config.js
+++ b/playwright.config.ts
@@ -1,4 +1,3 @@
-// @ts-check
 import {devices} from '@playwright/test';
 
 const BASE_URL = process.env.GITEA_URL?.replace?.(/\/$/g, '') || 'http://localhost:3000';
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000..88130812fe
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,30 @@
+{
+  "include": [
+    "*",
+    "tests/e2e/**/*",
+    "tools/**/*",
+    "web_src/js/**/*",
+  ],
+  "compilerOptions": {
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"],
+    "allowImportingTsExtensions": true,
+    "allowJs": true,
+    "allowSyntheticDefaultImports": true,
+    "alwaysStrict": true,
+    "esModuleInterop": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "resolveJsonModule": true,
+    "skipLibCheck": true,
+    "verbatimModuleSyntax": true,
+    "stripInternal": true,
+    "strict": false,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noPropertyAccessFromIndexSignature": false,
+    "exactOptionalPropertyTypes": false,
+  }
+}
diff --git a/vitest.config.js b/vitest.config.ts
similarity index 100%
rename from vitest.config.js
rename to vitest.config.ts
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 73aaa457f2..05120cb0cb 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -119,7 +119,7 @@ export function initRepoCommentForm() {
 
       hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
 
-      const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment
+      const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
       const scope = this.getAttribute('data-scope');
 
       $(this).parent().find('.item').each(function () {
@@ -416,7 +416,10 @@ async function onEditContent(event) {
         context: editContentZone.getAttribute('data-context'),
         content_version: editContentZone.getAttribute('data-content-version'),
       });
-      for (const fileInput of dropzoneInst?.element.querySelectorAll('.files [name=files]')) params.append('files[]', fileInput.value);
+      const files = dropzoneInst?.element?.querySelectorAll('.files [name=files]') ?? [];
+      for (const fileInput of files) {
+        params.append('files[]', fileInput.value);
+      }
 
       const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params});
       const data = await response.json();
diff --git a/web_src/js/webcomponents/overflow-menu.js b/web_src/js/webcomponents/overflow-menu.js
index a69ce1681c..54f8371927 100644
--- a/web_src/js/webcomponents/overflow-menu.js
+++ b/web_src/js/webcomponents/overflow-menu.js
@@ -4,7 +4,7 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.js';
 import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg';
 
 window.customElements.define('overflow-menu', class extends HTMLElement {
-  updateItems = throttle(100, () => {
+  updateItems = throttle(100, () => { // eslint-disable-line unicorn/consistent-function-scoping
     if (!this.tippyContent) {
       const div = document.createElement('div');
       div.classList.add('tippy-target');
diff --git a/webpack.config.js b/webpack.config.js
index ebbc51a381..4662a30db5 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -178,13 +178,13 @@ export default {
         },
       },
       {
-        test: /\.js$/i,
+        test: /\.(js|ts)$/i,
         exclude: /node_modules/,
         use: [
           {
             loader: 'esbuild-loader',
             options: {
-              loader: 'js',
+              loader: 'ts',
               target: 'es2020',
             },
           },