diff --git a/.github/workflows/nodelint.yml b/.github/workflows/nodelint.yml
index 786f9fea..a5d61413 100644
--- a/.github/workflows/nodelint.yml
+++ b/.github/workflows/nodelint.yml
@@ -17,6 +17,18 @@ jobs:
node-version: 24
- run: npm ci
- run: npm run ci-lint
+ run-typecheck:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./client
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 24
+ - run: npm ci
+ - run: npm run typecheck
run-node-lint-electron:
runs-on: ubuntu-latest
defaults:
diff --git a/client/babel.config.js b/client/babel.config.js
deleted file mode 100644
index d97bc1ad..00000000
--- a/client/babel.config.js
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = {
- presets: [
- '@babel/preset-env',
- ],
-};
diff --git a/client/eslint.config.mjs b/client/eslint.config.mjs
deleted file mode 100644
index 7c58db9d..00000000
--- a/client/eslint.config.mjs
+++ /dev/null
@@ -1,81 +0,0 @@
-import js from '@eslint/js';
-import pluginVue from 'eslint-plugin-vue';
-import globals from 'globals';
-import babelParser from '@babel/eslint-parser';
-import prettierConfig from 'eslint-config-prettier';
-import prettierPlugin from 'eslint-plugin-prettier';
-
-export default [
- {
- ignores: [
- '**/node_modules/**',
- '**/dist/**',
- '../server/static/**',
- 'junit/**',
- '*.backup',
- 'src/docs/**',
- ],
- },
- js.configs.recommended,
- ...pluginVue.configs['flat/vue2-recommended'],
- {
- files: ['**/*.{js,vue}'],
- plugins: {
- prettier: prettierPlugin,
- },
- languageOptions: {
- ecmaVersion: 2022,
- sourceType: 'module',
- parser: pluginVue.processors['.vue'].parser,
- parserOptions: {
- parser: babelParser,
- requireConfigFile: false,
- ecmaVersion: 13,
- sourceType: 'module',
- babelOptions: {
- presets: ['@babel/preset-env'],
- },
- },
- globals: {
- ...globals.browser,
- ...globals.node,
- ...globals.es2021,
- },
- },
- rules: {
- // Prettier integration - runs Prettier as an ESLint rule
- 'prettier/prettier': 'error',
-
- // Disable formatting rules that conflict with Prettier
- ...prettierConfig.rules,
-
- // Let Prettier handle line length (via printWidth config)
- 'max-len': 'off',
-
- // Custom linting rules (non-formatting)
- 'no-unused-vars': 'off',
- 'vue/no-unused-vars': 'off',
- 'no-plusplus': 'off',
- 'no-param-reassign': [
- 'error',
- {
- props: true,
- ignorePropertyModificationsFor: ['state', 'acc', 'e'],
- },
- ],
- },
- },
- {
- files: ['**/*.test.js'],
- languageOptions: {
- globals: {
- describe: 'readonly',
- it: 'readonly',
- expect: 'readonly',
- vi: 'readonly',
- beforeEach: 'readonly',
- afterEach: 'readonly',
- },
- },
- },
-];
\ No newline at end of file
diff --git a/client/eslint.config.ts b/client/eslint.config.ts
new file mode 100644
index 00000000..3e26e78e
--- /dev/null
+++ b/client/eslint.config.ts
@@ -0,0 +1,100 @@
+import js from '@eslint/js';
+import pluginVue from 'eslint-plugin-vue';
+import globals from 'globals';
+import tsParser from '@typescript-eslint/parser';
+import tsPlugin from '@typescript-eslint/eslint-plugin';
+import prettierConfig from 'eslint-config-prettier';
+import prettierPlugin from 'eslint-plugin-prettier';
+
+const sharedRules = {
+ 'prettier/prettier': 'error',
+ ...prettierConfig.rules,
+ 'max-len': 'off',
+ 'no-unused-vars': 'off',
+ 'vue/no-unused-vars': 'off',
+ 'no-plusplus': 'off',
+ 'no-param-reassign': [
+ 'error',
+ {
+ props: true,
+ ignorePropertyModificationsFor: ['state', 'acc', 'e'],
+ },
+ ],
+};
+
+const tsRules = {
+ ...tsPlugin.configs.recommended.rules,
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
+ // Vue 2 Options API patterns that are valid in this codebase
+ '@typescript-eslint/no-this-alias': 'off',
+ '@typescript-eslint/ban-types': 'off',
+};
+
+const sharedGlobals = {
+ ...globals.browser,
+ ...globals.node,
+ ...globals.es2021,
+};
+
+export default [
+ {
+ ignores: [
+ '**/node_modules/**',
+ '**/dist/**',
+ '../server/static/**',
+ 'junit/**',
+ '*.backup',
+ 'src/docs/**',
+ ],
+ },
+ js.configs.recommended,
+ ...pluginVue.configs['flat/vue2-recommended'],
+ // TypeScript source files
+ {
+ files: ['**/*.ts'],
+ plugins: {
+ '@typescript-eslint': tsPlugin,
+ prettier: prettierPlugin,
+ },
+ languageOptions: {
+ parser: tsParser,
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+ globals: sharedGlobals,
+ },
+ rules: { ...tsRules, ...sharedRules },
+ },
+ // Vue SFCs — all use
+