diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2d7e07c383..2840a631eb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -184,8 +184,49 @@ jobs:
         run: npm run test:cov
         if: ${{ !cancelled() }}
 
+  web-lint:
+    name: Lint Web
+    needs: pre-job
+    if: ${{ needs.pre-job.outputs.should_run_web == 'true' }}
+    runs-on: mich
+    permissions:
+      contents: read
+    defaults:
+      run:
+        working-directory: ./web
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+        with:
+          persist-credentials: false
+
+      - name: Setup Node
+        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+        with:
+          node-version-file: './web/.nvmrc'
+
+      - name: Run setup typescript-sdk
+        run: npm ci && npm run build
+        working-directory: ./open-api/typescript-sdk
+
+      - name: Run npm install
+        run: npm ci
+
+      - name: Run linter
+        run: npm run lint:p
+        if: ${{ !cancelled() }}
+
+      - name: Run formatter
+        run: npm run format
+        if: ${{ !cancelled() }}
+
+      - name: Run svelte checks
+        run: npm run check:svelte
+        if: ${{ !cancelled() }}
+
   web-unit-tests:
-    name: Test & Lint Web
+    name: Test Web
     needs: pre-job
     if: ${{ needs.pre-job.outputs.should_run_web == 'true' }}
     runs-on: ubuntu-latest
@@ -213,18 +254,6 @@ jobs:
       - name: Run npm install
         run: npm ci
 
-      - name: Run linter
-        run: npm run lint
-        if: ${{ !cancelled() }}
-
-      - name: Run formatter
-        run: npm run format
-        if: ${{ !cancelled() }}
-
-      - name: Run svelte checks
-        run: npm run check:svelte
-        if: ${{ !cancelled() }}
-
       - name: Run tsc
         run: npm run check:typescript
         if: ${{ !cancelled() }}
diff --git a/web/package-lock.json b/web/package-lock.json
index fb6a059197..af87772105 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -59,6 +59,7 @@
         "dotenv": "^16.4.7",
         "eslint": "^9.18.0",
         "eslint-config-prettier": "^10.0.0",
+        "eslint-p": "^0.21.0",
         "eslint-plugin-svelte": "^3.0.0",
         "eslint-plugin-unicorn": "^57.0.0",
         "factory.ts": "^1.4.1",
@@ -692,9 +693,9 @@
       }
     },
     "node_modules/@eslint/core": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
-      "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
+      "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
       "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
@@ -742,9 +743,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "9.24.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz",
-      "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==",
+      "version": "9.25.1",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz",
+      "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -775,19 +776,6 @@
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
-    "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
-      "version": "0.13.0",
-      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
-      "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@types/json-schema": "^7.0.15"
-      },
-      "engines": {
-        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
-      }
-    },
     "node_modules/@faker-js/faker": {
       "version": "9.7.0",
       "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz",
@@ -4189,20 +4177,20 @@
       }
     },
     "node_modules/eslint": {
-      "version": "9.24.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
-      "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
+      "version": "9.25.1",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz",
+      "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.12.1",
         "@eslint/config-array": "^0.20.0",
-        "@eslint/config-helpers": "^0.2.0",
-        "@eslint/core": "^0.12.0",
+        "@eslint/config-helpers": "^0.2.1",
+        "@eslint/core": "^0.13.0",
         "@eslint/eslintrc": "^3.3.1",
-        "@eslint/js": "9.24.0",
-        "@eslint/plugin-kit": "^0.2.7",
+        "@eslint/js": "9.25.1",
+        "@eslint/plugin-kit": "^0.2.8",
         "@humanfs/node": "^0.16.6",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@humanwhocodes/retry": "^0.4.2",
@@ -4262,6 +4250,22 @@
         "eslint": ">=7.0.0"
       }
     },
+    "node_modules/eslint-p": {
+      "version": "0.21.0",
+      "resolved": "https://registry.npmjs.org/eslint-p/-/eslint-p-0.21.0.tgz",
+      "integrity": "sha512-w6krTooDpMF5Rezfc14FOQwX87QyH2ouZoTDySixbL8auiBRsxA4JPBrzOS5IfuLxcJHJPHjf/FnGfjlvI/kDw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "eslint": "9.25.1"
+      },
+      "bin": {
+        "eslint-p": "lib/eslint-p.js"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
     "node_modules/eslint-plugin-svelte": {
       "version": "3.5.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.5.1.tgz",
diff --git a/web/package.json b/web/package.json
index 4ee7c015c8..80610e661e 100644
--- a/web/package.json
+++ b/web/package.json
@@ -15,6 +15,7 @@
     "check:code": "npm run format && npm run lint && npm run check:svelte && npm run check:typescript",
     "check:all": "npm run check:code && npm run test:cov",
     "lint": "eslint . --max-warnings 0",
+    "lint:p": "eslint-p . --max-warnings 0 --concurrency=4",
     "lint:fix": "npm run lint -- --fix",
     "format": "prettier --check .",
     "format:fix": "prettier --write .",
@@ -74,6 +75,7 @@
     "dotenv": "^16.4.7",
     "eslint": "^9.18.0",
     "eslint-config-prettier": "^10.0.0",
+    "eslint-p": "^0.21.0",
     "eslint-plugin-svelte": "^3.0.0",
     "eslint-plugin-unicorn": "^57.0.0",
     "factory.ts": "^1.4.1",