diff --git a/.github/workflows/backend_ci.yml b/.github/workflows/backend_ci.yml
new file mode 100644
index 00000000..cb590b0f
--- /dev/null
+++ b/.github/workflows/backend_ci.yml
@@ -0,0 +1,39 @@
+name: Check for project
+description: Check for type errors and linting errors in the project.
+
+on:
+ pull_request:
+ paths:
+ - "backend/**"
+ - ".github/workflows/**"
+ - "pnpm-lock.yaml"
+ workflow_dispatch:
+permissions:
+ contents: read
+
+jobs:
+ ci:
+ name: Backend CI
+ timeout-minutes: 10
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: "package.json"
+ run_install: false
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: "package.json"
+ cache: "pnpm"
+ cache-dependency-path: "pnpm-lock.yaml"
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+ - name: Build backend
+ run: pnpm run --filter backend build # If this fails, the PR should not be merged.
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 4249ebbb..00000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,145 +0,0 @@
-name: Check for project
-description: Check for type errors and linting errors in the project.
-
-on:
- pull_request:
- branches:
- - main
- - kei
- workflow_dispatch:
-permissions:
- contents: read
-
-jobs:
- buildable:
- timeout-minutes: 10
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- package_json_file: "package.json"
- run_install: false
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version-file: "package.json"
- cache: "pnpm"
- cache-dependency-path: "pnpm-lock.yaml"
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
- - name: Build frontend
- run: pnpm run --filter frontend build # If this fails, the PR should not be merged.
- - name: Build backend
- run: pnpm run --filter backend build # If this fails, the PR should not be merged.
- ci:
- timeout-minutes: 15
- runs-on: ubuntu-latest
- container:
- image: mcr.microsoft.com/playwright:v1.57.0
- options: --user 1001
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- package_json_file: "package.json"
- run_install: false
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version-file: "package.json"
- cache: "pnpm"
- cache-dependency-path: "pnpm-lock.yaml"
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Check type errors
- timeout-minutes: 10 # It takes long time due to svelte type checking
- run: pnpm run --filter frontend check:types
-
- - name: Run unit tests
- run: pnpm run --filter frontend test --reporter=dot
- timeout-minutes: 5
-
- - name: Run browser tests
- run: pnpm run --filter frontend test:browser --reporter=dot
- timeout-minutes: 10 # Browser is slow
-
- - name: Check linting
- run: pnpm run --filter frontend lint
- timeout-minutes: 5
- coverage:
- timeout-minutes: 15
- runs-on: ubuntu-latest
- permissions:
- contents: read
- pull-requests: write
- statuses: write
- container:
- image: mcr.microsoft.com/playwright:v1.57.0
- options: --user 1001
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- package_json_file: "package.json"
- run_install: false
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version-file: "package.json"
- cache: "pnpm"
- cache-dependency-path: "pnpm-lock.yaml"
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Run coverage check
- run: pnpm run --filter frontend test:coverage
- timeout-minutes: 5
-
- - name: Update Coverage Status
- if: always()
- uses: actions/github-script@v7
- with:
- script: |
- // 1. read coverage summary
- const fs = require('fs');
- const summaryPath = './frontend/coverage/coverage-summary.json';
-
- if (!fs.existsSync(summaryPath)) {
- console.log('No coverage summary found.');
- return;
- }
-
- const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
-
- // 2. Total coverage % calc
- const total = summary.total.lines.pct;
- const color = total >= 75 ? 'success' : 'failure'; // Green if >= 75%
- const description = `Lines: ${total}% | Statements: ${summary.total.statements.pct}%`;
-
- // 3. Github PR checklist update
- await github.rest.repos.createCommitStatus({
- owner: context.repo.owner,
- repo: context.repo.repo,
- sha: context.sha,
- state: color,
- context: 'Vitest Coverage', // List name
- description: description, // e.g. "Lines: 85%"
- target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` // Click to see details
- });
\ No newline at end of file
diff --git a/.github/workflows/flaky_test_check.yml b/.github/workflows/flaky_test_check.yml
index a14acdee..8d5e6ac3 100644
--- a/.github/workflows/flaky_test_check.yml
+++ b/.github/workflows/flaky_test_check.yml
@@ -1,68 +1,66 @@
-name: Flaky Test Stress Test
-
-on:
- workflow_dispatch:
- inputs:
- iterations:
- description: |
- Number of parallel iterations to run.
- Note that iterations above 20 require GitHub Pro or better to run in parallel.
- Range: 1..200
- required: true
- default: 10
- type: number
-permissions:
- contents: read
-jobs:
- setup-matrix:
- runs-on: ubuntu-latest
- outputs:
- indices: ${{ steps.set-matrix.outputs.indices }}
- steps:
- - id: set-matrix
- run: |
- ITER="${{ github.event.inputs.iterations }}"
- if [ "$ITER" -lt 1 ] || [ "$ITER" -gt 200 ]; then
- echo "Error: iterations must be between 1 and 200."
- exit 1
- fi
- # Generate a JSON array [1, 2, ..., N]
- INDICES=$(node -e "console.log(JSON.stringify(Array.from({length: $ITER}, (_, i) => i + 1)))")
- echo "indices=$INDICES" >> $GITHUB_OUTPUT
-
- run-flaky-check:
- needs: setup-matrix
- runs-on: ubuntu-latest
- timeout-minutes: 30
- strategy:
- fail-fast: false # Don't stop other jobs if one fails, we want to see all fails
- matrix:
- index: ${{ fromJson(needs.setup-matrix.outputs.indices) }}
- container:
- image: mcr.microsoft.com/playwright:v1.57.0
- options: --user 1001
- steps:
- - uses: actions/checkout@v4
-
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- package_json_file: "package.json"
- run_install: false
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version-file: "package.json"
- cache: "pnpm"
- cache-dependency-path: "pnpm-lock.yaml"
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Run Tests (Iteration ${{ matrix.index }})
- run: |
- pnpm -F frontend test:browser
- pnpm -F frontend test:coverage --coverage.reporters=text-summary
- pnpm -F frontend test
- timeout-minutes: 10
+name: Flaky Test Stress Test
+
+on:
+ workflow_dispatch:
+ inputs:
+ iterations:
+ description: |
+ Number of parallel iterations to run.
+ Note that iterations above 20 require GitHub Pro or better to run in parallel.
+ Range: 1..200
+ required: true
+ default: 10
+ type: number
+permissions:
+ contents: read
+jobs:
+ setup-matrix:
+ runs-on: ubuntu-latest
+ outputs:
+ indices: ${{ steps.set-matrix.outputs.indices }}
+ steps:
+ - id: set-matrix
+ run: |
+ ITER="${{ github.event.inputs.iterations }}"
+ if [ "$ITER" -lt 1 ] || [ "$ITER" -gt 200 ]; then
+ echo "Error: iterations must be between 1 and 200."
+ exit 1
+ fi
+ # Generate a JSON array [1, 2, ..., N]
+ INDICES=$(node -e "console.log(JSON.stringify(Array.from({length: $ITER}, (_, i) => i + 1)))")
+ echo "indices=$INDICES" >> $GITHUB_OUTPUT
+
+ run-flaky-check:
+ needs: setup-matrix
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false # Don't stop other jobs if one fails, we want to see all fails
+ matrix:
+ index: ${{ fromJson(needs.setup-matrix.outputs.indices) }}
+ container:
+ image: mcr.microsoft.com/playwright:v1.57.0
+ options: --user 1001
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: "package.json"
+ run_install: false
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: "package.json"
+ cache: "pnpm"
+ cache-dependency-path: "pnpm-lock.yaml"
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Run Tests (Iteration ${{ matrix.index }})
+ run: |
+ pnpm -F frontend test --coverage.reporters=text-summary
+ timeout-minutes: 10
diff --git a/.github/workflows/frontend_ci.yml b/.github/workflows/frontend_ci.yml
new file mode 100644
index 00000000..d87a76c3
--- /dev/null
+++ b/.github/workflows/frontend_ci.yml
@@ -0,0 +1,171 @@
+name: Frontend CI
+on:
+ pull_request:
+ paths:
+ - "frontend/**"
+ - ".github/workflows/**"
+ - "pnpm-lock.yaml"
+
+jobs:
+ setup:
+ name: Setup Cache
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: frontend/package.json
+ run_install: false
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: frontend/package.json
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+ working-directory: frontend
+
+ - name: Cache Playwright browsers
+ id: playwright-cache
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: ${{ runner.os }}-playwright-browsers-${{ hashFiles('pnpm-lock.yaml') }}
+
+ - name: Install Playwright browsers
+ if: steps.playwright-cache.outputs.cache-hit != 'true'
+ run: pnpm exec playwright install
+ working-directory: frontend
+
+ lint:
+ name: Frontend Lint
+ needs: [setup]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: frontend/package.json
+ run_install: false
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: frontend/package.json
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+ working-directory: frontend
+
+ - name: Lint
+ run: pnpm -F frontend run lint
+
+ - name: Type check
+ run: pnpm -F frontend run check:types
+
+ tests:
+ name: Frontend Tests
+ needs: [setup]
+ permissions:
+ contents: read
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ shardIndex: [1, 2, 3, 4, 5]
+ shardTotal: [5]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: frontend/package.json
+ run_install: false
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: frontend/package.json
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+ working-directory: frontend
+
+ - name: Restore Playwright browsers
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: ${{ runner.os }}-playwright-browsers-${{ hashFiles('pnpm-lock.yaml') }}
+
+ - name: Install Playwright system dependencies
+ run: pnpm exec playwright install-deps
+ working-directory: frontend
+
+ - name: Run tests
+ run: pnpm -F frontend run test --reporter=blob --coverage.enabled --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
+
+ - name: Upload blob report
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: blob-report-${{ matrix.shardIndex }}
+ path: frontend/.vitest-reports/*
+ include-hidden-files: true
+ retention-days: 1
+
+ merge-reports:
+ name: Merge Reports
+ if: ${{ !cancelled() }}
+ needs: [tests]
+ permissions:
+ pull-requests: write
+ statuses: write
+ contents: read
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: frontend/package.json
+ run_install: false
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: frontend/package.json
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+ working-directory: frontend
+
+ - name: Download blob reports
+ uses: actions/download-artifact@v4
+ with:
+ path: frontend/.vitest-reports
+ pattern: blob-report-*
+ merge-multiple: true
+
+ - name: Merge reports
+ run: pnpm -F frontend run test --merge-reports --reporter=github-actions --reporter=default --coverage.reporter=text --coverage.reporter=json-summary
+
+ - name: Update Coverage Status
+ if: ${{ github.event_name == 'pull_request' && always() }}
+ uses: concertypin/action/report-vitest-coverage@master
+ with:
+ json-file: frontend/coverage/coverage-summary.json
+ threshold: 80
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 13a2e6e9..2b01a393 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -3,6 +3,7 @@
"svelte.svelte-vscode",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
- "vitest.explorer"
+ "vitest.explorer",
+ "oxc.oxc-vscode"
]
}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 56e0e4f1..bc5fb756 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,27 +9,28 @@
},
"[svelte]": {
"editor.tabSize": 4,
- "editor.defaultFormatter": "svelte.svelte-vscode"
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 4
},
+ "[github-actions-workflow]": {
+ "editor.tabSize": 2
+ },
"typescript.preferences.preferTypeOnlyAutoImports": true,
"js/ts.implicitProjectConfig.target": "ESNext",
"typescript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifierEnding": "minimal",
"typescript.preferences.importModuleSpecifierEnding": "minimal",
- "vitest.nodeEnv": {
- // Enabling browser test on VSC vitest,
- // since it will not run browser tests by default
- "npm_lifecycle_event": "coverage",
- "IS_IDE": "vscode"
- },
- "eslint.workingDirectories": [
- {
- "directory": "./frontend/",
- }
- ]
+ "oxc.typeAware": true,
+ "eslint.validate": [
+ "javascript",
+ "typescript",
+ "svelte"
+ ],
+ "files.exclude": {
+ "coverage": true
+ }
}
\ No newline at end of file
diff --git a/frontend/.oxlintrc.json b/frontend/.oxlintrc.json
new file mode 100644
index 00000000..c93f6e30
--- /dev/null
+++ b/frontend/.oxlintrc.json
@@ -0,0 +1,35 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "extends": [
+ "./scripts/linter/oxlint-eslint-error.json",
+ "./scripts/linter/oxlint-eslint-warn.json",
+ "./scripts/linter/oxlint-svelte.json"
+ ],
+ "ignorePatterns": [
+ "**/node_modules/**",
+ "**/dist/**",
+ "**/coverage/**",
+ "**/.cache/**"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "**/*.d.ts"
+ ],
+ "rules": {
+ "no-unused-vars": "off"
+ }
+ },
+ {
+ "files": [
+ "**/test/**",
+ "**/*.test.ts",
+ "**/*.spec.ts"
+ ],
+ "rules": {
+ "jest/require-to-throw-message": "off",
+ "jest/expect-expect": "off"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend/README.md b/frontend/README.md
index f563e205..ba45341f 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -1,63 +1,63 @@
-# ArisuTalk Frontend ๐จ
-
-This is the frontend for ArisuTalk, built with **Svelte 5** and lots of love! โจ
-
-## ๐ธ Project Kei
-
-This frontend is currently being rebuilt as part of **Project Kei**! ๐ We're using modern tools and a clean architecture to provide the best possible roleplay experience.
-
-## ๐ ๏ธ Tech Stack
-
-- **Framework:** Svelte 5 (Runes)
-- **Build Tool:** Vite
-- **Styling:** Tailwind CSS + DaisyUI
-- **Icons:** `@lucide/svelte`
-- **Storage:** Dexie.js (IndexedDB)
-- **Workers:** Comlink
-
-## ๐ Development
-
-### Prerequisites
-
-- Node.js (>20)
-- pnpm (10.14.0+)
-
-### Setup
-
-```bash
-pnpm install
-```
-
-### Run Dev Server
-
-```bash
-pnpm run dev
-```
-
-The server will start at `http://localhost:5173` ๐
-
-### Testing
-
-We take testing seriously! ๐งช
-
-- **Unit Tests:** `pnpm run test`
-- **Browser Tests:** `pnpm run test:browser`
-- **Coverage:** `pnpm run test:coverage`
-
-### Linting & Formatting
-
-```bash
-pnpm run lint
-pnpm run format
-```
-
-## ๐ Structure
-
-- `src/components`: UI components ๐งฉ
-- `src/features`: Feature-based logic (character, chat, persona) ๐
-- `src/lib`: Shared utilities, adapters, and stores ๐ ๏ธ
-- `worker/`: Web workers for heavy tasks ๐ทโโ๏ธ
-
----
-
-Happy coding! ๐
+# ArisuTalk Frontend ๐จ
+
+This is the frontend for ArisuTalk, built with **Svelte 5** and lots of love! โจ
+
+## ๐ธ Project Kei
+
+This frontend is currently being rebuilt as part of **Project Kei**! ๐ We're using modern tools and a clean architecture to provide the best possible roleplay experience.
+
+## ๐ ๏ธ Tech Stack
+
+- **Framework:** Svelte 5 (Runes)
+- **Build Tool:** Vite
+- **Styling:** Tailwind CSS + DaisyUI
+- **Icons:** `@lucide/svelte`
+- **Storage:** Dexie.js (IndexedDB)
+- **Workers:** Comlink
+
+## ๐ Development
+
+### Prerequisites
+
+- Node.js (>=22.18.0 <23.0.0 || >=23.6.0), which supports type stripping without flag.
+- pnpm (10.14.0+)
+
+### Setup
+
+```bash
+pnpm install
+```
+
+### Run Dev Server
+
+```bash
+pnpm run dev
+```
+
+The server will start at `http://localhost:5173` ๐
+
+### Testing
+
+We take testing seriously! ๐งช
+
+- **Unit Tests:** `pnpm run test`
+- **Browser Tests:** `pnpm run test:browser`
+- **Coverage:** `pnpm run test:coverage`
+
+### Linting & Formatting
+
+```bash
+pnpm run lint
+pnpm run format
+```
+
+## ๐ Structure
+
+- `src/components`: UI components ๐งฉ
+- `src/features`: Feature-based logic (character, chat, persona) ๐
+- `src/lib`: Shared utilities, adapters, and stores ๐ ๏ธ
+- `worker/`: Web workers for heavy tasks ๐ทโโ๏ธ
+
+---
+
+Happy coding! ๐
diff --git a/frontend/common/logger/LogBridge.ts b/frontend/common/logger/LogBridge.ts
index ccb2adf9..d328ad61 100644
--- a/frontend/common/logger/LogBridge.ts
+++ b/frontend/common/logger/LogBridge.ts
@@ -1,5 +1,6 @@
+// oxlint-disable no-console
// It's logger, so we should use console
-/* eslint-disable no-console */
+
import type { StandardLogEntry, LogLevel, StructuredLogEntry } from "./Logger";
import type { StructuredLogLevel } from "./LogType";
diff --git a/frontend/docs/rules/tools.md b/frontend/docs/rules/tools.md
new file mode 100644
index 00000000..05cb155e
--- /dev/null
+++ b/frontend/docs/rules/tools.md
@@ -0,0 +1,89 @@
+# Tools Configuration
+
+This document provides guidelines for configuring the linting and formatting tools in the ArisuTalk frontend project.
+
+## Linter Configuration
+
+This project uses **oxlint** as the primary linter for fast, type-aware linting, with **ESLint** as a fallback for rules that oxlint doesn't support yet.
+
+### Oxlint
+
+Fast linter for TypeScript and JavaScript, built on the Oxc compiler stack.
+
+**Supports:**
+
+- Most typescript-eslint rules, including type-aware rules
+- ESLint's JS plugins via the `jsPlugins` feature
+- Plugins: `typescript`, `unicorn`, `import`, `vitest`, `promise`
+
+**Doesn't support:**
+
+- Custom file formats and parsers (Svelte, Vue, Angular templates)
+- Some HTML-superset code (only checks `
diff --git a/frontend/src/components/settingSubpage/LLMSetting/GenerationParameters.svelte b/frontend/src/components/settingSubpage/LLMSetting/GenerationParameters.svelte
index 42791766..60f25468 100644
--- a/frontend/src/components/settingSubpage/LLMSetting/GenerationParameters.svelte
+++ b/frontend/src/components/settingSubpage/LLMSetting/GenerationParameters.svelte
@@ -10,7 +10,7 @@
id: number;
};
- let { config = $bindable(), id }: Props = $props();
+ const { config = $bindable(), id }: Props = $props();
/**
* Creates a proxy object to bind a checkbox to the presence of a field in a target object.
@@ -48,22 +48,22 @@
let isActive = $derived(settings.value.activeLLMConfigId === config.id);
- const modelProxy = $derived(createFieldProxy(config, "model"));
- const keyProxy = $derived(createFieldProxy(config, "apiKey"));
- const urlProxy = $derived(createFieldProxy(config, "baseURL"));
- const tempProxy = $derived(createFieldProxy(config.generationParameters, "temperature", 1));
- const maxInProxy = $derived(
+ let modelProxy = $derived(createFieldProxy(config, "model"));
+ let keyProxy = $derived(createFieldProxy(config, "apiKey"));
+ let urlProxy = $derived(createFieldProxy(config, "baseURL"));
+ let tempProxy = $derived(createFieldProxy(config.generationParameters, "temperature", 1));
+ let maxInProxy = $derived(
createFieldProxy(config.generationParameters, "maxInputTokens", 1024)
);
- const maxOutProxy = $derived(
+ let maxOutProxy = $derived(
createFieldProxy(config.generationParameters, "maxOutputTokens", 1024)
);
- const topPProxy = $derived(createFieldProxy(config.generationParameters, "topP", 0.95));
- const topKProxy = $derived(createFieldProxy(config.generationParameters, "topK", 40));
- const freqPenProxy = $derived(
+ let topPProxy = $derived(createFieldProxy(config.generationParameters, "topP", 0.95));
+ let topKProxy = $derived(createFieldProxy(config.generationParameters, "topK", 40));
+ let freqPenProxy = $derived(
createFieldProxy(config.generationParameters, "frequencyPenalty", 0)
);
- const presPenProxy = $derived(
+ let presPenProxy = $derived(
createFieldProxy(config.generationParameters, "presencePenalty", 0)
);
diff --git a/frontend/src/components/ui/Button.svelte b/frontend/src/components/ui/Button.svelte
index 182253d7..e9599d3a 100644
--- a/frontend/src/components/ui/Button.svelte
+++ b/frontend/src/components/ui/Button.svelte
@@ -20,7 +20,7 @@
children: Snippet;
};
- let {
+ const {
variant = "primary",
size = "md",
disabled = false,
diff --git a/frontend/src/features/character/adapters/storage/LocalStorageAdapter.ts b/frontend/src/features/character/adapters/storage/LocalStorageAdapter.ts
index 1ac35b90..38d3ccc3 100644
--- a/frontend/src/features/character/adapters/storage/LocalStorageAdapter.ts
+++ b/frontend/src/features/character/adapters/storage/LocalStorageAdapter.ts
@@ -154,7 +154,7 @@ export class LocalStorageAdapter {
}
} catch (e) {
Logger.error("Failed to import data", e);
- throw new Error("Invalid data format");
+ throw new Error("Invalid data format", { cause: e });
}
}
}
diff --git a/frontend/src/features/character/components/CharacterCard.svelte b/frontend/src/features/character/components/CharacterCard.svelte
index 62e41632..9a43aa23 100644
--- a/frontend/src/features/character/components/CharacterCard.svelte
+++ b/frontend/src/features/character/components/CharacterCard.svelte
@@ -17,7 +17,7 @@
isFirst?: boolean;
isLast?: boolean;
};
- let {
+ const {
character,
onEdit,
onDelete,
diff --git a/frontend/src/features/character/components/CharacterForm.svelte b/frontend/src/features/character/components/CharacterForm.svelte
index e5749e80..ebca6c48 100644
--- a/frontend/src/features/character/components/CharacterForm.svelte
+++ b/frontend/src/features/character/components/CharacterForm.svelte
@@ -10,7 +10,7 @@
onSubmit?: (character: Character) => void;
};
- let { character = undefined, onSave, onCancel, onSubmit }: Props = $props();
+ const { character = undefined, onSave, onCancel, onSubmit }: Props = $props();
let name = $state("");
let description = $state("");
diff --git a/frontend/src/features/character/components/CharacterLayout.svelte b/frontend/src/features/character/components/CharacterLayout.svelte
index cfe542b7..d1200ec4 100644
--- a/frontend/src/features/character/components/CharacterLayout.svelte
+++ b/frontend/src/features/character/components/CharacterLayout.svelte
@@ -15,7 +15,7 @@
children?: import("svelte").Snippet;
};
- let { children }: Props = $props();
+ const { children }: Props = $props();
let selectedCharacterId = $state(null);
let dialog = $state();
diff --git a/frontend/src/features/character/components/CharacterList.svelte b/frontend/src/features/character/components/CharacterList.svelte
index e4ceb2ba..de301f0d 100644
--- a/frontend/src/features/character/components/CharacterList.svelte
+++ b/frontend/src/features/character/components/CharacterList.svelte
@@ -12,8 +12,8 @@
onEdit: (index: number) => void;
};
- let { onEdit }: Props = $props();
- let worker = getCardParseWorker();
+ const { onEdit }: Props = $props();
+ const worker = getCardParseWorker();
async function handleDelete(index: number) {
const modal = document.getElementById("delete-confirm-modal") as HTMLDialogElement;
diff --git a/frontend/src/features/character/components/CharacterSidebar.svelte b/frontend/src/features/character/components/CharacterSidebar.svelte
index 87445be4..becbc184 100644
--- a/frontend/src/features/character/components/CharacterSidebar.svelte
+++ b/frontend/src/features/character/components/CharacterSidebar.svelte
@@ -15,7 +15,7 @@
onPersona: () => void;
};
- let { selectedCharacterId, onSelect, onAdd, onPersona }: Props = $props();
+ const { selectedCharacterId, onSelect, onAdd, onPersona }: Props = $props();
const flipDurationMs = 200;
diff --git a/frontend/src/features/character/components/CharacterSidebarItem.svelte b/frontend/src/features/character/components/CharacterSidebarItem.svelte
index 2c3e2730..35fafc39 100644
--- a/frontend/src/features/character/components/CharacterSidebarItem.svelte
+++ b/frontend/src/features/character/components/CharacterSidebarItem.svelte
@@ -9,7 +9,7 @@
onClick: () => void;
};
- let { character, active, onClick }: Props = $props();
+ const { character, active, onClick }: Props = $props();
// Generate initials from name
let initials = $derived(character.name.substring(0, 2).toUpperCase());
diff --git a/frontend/src/features/character/components/settingsSubpage/CharacterAssetsSettings.svelte b/frontend/src/features/character/components/settingsSubpage/CharacterAssetsSettings.svelte
index 1ede81a7..9b4d6cc8 100644
--- a/frontend/src/features/character/components/settingsSubpage/CharacterAssetsSettings.svelte
+++ b/frontend/src/features/character/components/settingsSubpage/CharacterAssetsSettings.svelte
@@ -20,13 +20,13 @@
onChange: (character: Character) => void;
};
- let { character, onChange }: Props = $props();
+ const { character, onChange }: Props = $props();
let fileInput = $state();
let expandOtherAssets = $state(false);
let duplicateNameError = $state(null);
let draggedIndex = $state(null);
- let assetPreviews = new SvelteMap();
+ const assetPreviews = new SvelteMap();
const assetStorage = getAssetStorage();
@@ -48,15 +48,15 @@
void loadPreviews();
});
- const imageAssets = $derived(
+ let imageAssets = $derived(
character.assets.assets.filter((a) => a.mimeType.startsWith("image/"))
);
- const otherAssets = $derived(
+ let otherAssets = $derived(
character.assets.assets.filter((a) => !a.mimeType.startsWith("image/"))
);
- async function handleFileUpload(e: Event) {
- const target = e.target as HTMLInputElement;
+ async function handleFileUpload(ev: Event) {
+ const target = ev.target as HTMLInputElement;
const files = target.files;
if (!files || files.length === 0) return;
diff --git a/frontend/src/features/character/components/settingsSubpage/CharacterBasicSettings.svelte b/frontend/src/features/character/components/settingsSubpage/CharacterBasicSettings.svelte
index e7ea2123..9d6e6ff6 100644
--- a/frontend/src/features/character/components/settingsSubpage/CharacterBasicSettings.svelte
+++ b/frontend/src/features/character/components/settingsSubpage/CharacterBasicSettings.svelte
@@ -16,13 +16,13 @@
onChange: (character: Character) => void;
};
- let { character, onChange }: Props = $props();
+ const { character, onChange }: Props = $props();
const assetStorage = getAssetStorage();
- let assetPreviews = new SvelteMap();
+ const assetPreviews = new SvelteMap();
let showManualUrl = $state(false);
- const imageAssets = $derived(
+ let imageAssets = $derived(
character.assets.assets.filter((a) => a.mimeType.startsWith("image/"))
);
diff --git a/frontend/src/features/character/components/settingsSubpage/CharacterHooksSettings.svelte b/frontend/src/features/character/components/settingsSubpage/CharacterHooksSettings.svelte
index ceb75338..3a23b120 100644
--- a/frontend/src/features/character/components/settingsSubpage/CharacterHooksSettings.svelte
+++ b/frontend/src/features/character/components/settingsSubpage/CharacterHooksSettings.svelte
@@ -21,7 +21,7 @@
onChange: (character: Character) => void;
};
- let { character, onChange }: Props = $props();
+ const { character, onChange }: Props = $props();
let activeHookType = $state("display");
let expandedHookIndex = $state(null);
diff --git a/frontend/src/features/character/components/settingsSubpage/CharacterLorebookSettings.svelte b/frontend/src/features/character/components/settingsSubpage/CharacterLorebookSettings.svelte
index 7213b09c..d0d8d67c 100644
--- a/frontend/src/features/character/components/settingsSubpage/CharacterLorebookSettings.svelte
+++ b/frontend/src/features/character/components/settingsSubpage/CharacterLorebookSettings.svelte
@@ -19,7 +19,7 @@
onChange: (character: Character) => void;
};
- let { character, onChange }: Props = $props();
+ const { character, onChange }: Props = $props();
let expandedEntryId = $state(null);
diff --git a/frontend/src/features/character/components/settingsSubpage/CharacterMetadataSettings.svelte b/frontend/src/features/character/components/settingsSubpage/CharacterMetadataSettings.svelte
index 6f7bfbd1..449dafcb 100644
--- a/frontend/src/features/character/components/settingsSubpage/CharacterMetadataSettings.svelte
+++ b/frontend/src/features/character/components/settingsSubpage/CharacterMetadataSettings.svelte
@@ -11,7 +11,7 @@
onChange: (character: Character) => void;
};
- let { character, onChange }: Props = $props();
+ const { character, onChange }: Props = $props();
/** Common license options for autocomplete */
const licenseOptions = [
diff --git a/frontend/src/features/character/components/settingsSubpage/CharacterPromptSettings.svelte b/frontend/src/features/character/components/settingsSubpage/CharacterPromptSettings.svelte
index 27ba0104..3cf76a75 100644
--- a/frontend/src/features/character/components/settingsSubpage/CharacterPromptSettings.svelte
+++ b/frontend/src/features/character/components/settingsSubpage/CharacterPromptSettings.svelte
@@ -14,7 +14,7 @@
onChange: (character: Character) => void;
};
- let { character, onChange }: Props = $props();
+ const { character, onChange }: Props = $props();
let isDescriptionExpanded = $state(false);
let isAuthorsNoteExpanded = $state(false);
diff --git a/frontend/src/features/character/stores/characterStore.svelte.ts b/frontend/src/features/character/stores/characterStore.svelte.ts
index 15af8762..ffc89cbf 100644
--- a/frontend/src/features/character/stores/characterStore.svelte.ts
+++ b/frontend/src/features/character/stores/characterStore.svelte.ts
@@ -44,11 +44,12 @@ export class CharacterStore {
// Sort by saved order
const order = this.getOrder();
if (order.length > 0) {
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
- const orderMap = new Map(order.map((id, index) => [id, index]));
+ const orderMap: Record = Object.fromEntries(
+ order.map((id, index) => [id, index])
+ );
chars.sort((a, b) => {
- const idxA = orderMap.get(a.id);
- const idxB = orderMap.get(b.id);
+ const idxA = orderMap[a.id];
+ const idxB = orderMap[b.id];
// If both present, sort by index
if (idxA !== undefined && idxB !== undefined) return idxA - idxB;
// If only one present, present goes first? Or last?
@@ -131,14 +132,13 @@ export class CharacterStore {
success: true,
});
return { success: true };
- } else {
- Logger.structured("character.import", {
- format,
- success: false,
- errorMessage: "Failed to parse character",
- });
- return { success: false, error: "Failed to parse character" };
}
+ Logger.structured("character.import", {
+ format,
+ success: false,
+ errorMessage: "Failed to parse character",
+ });
+ return { success: false, error: "Failed to parse character" };
} catch (e) {
Logger.error(e);
Logger.structured("character.import", {
diff --git a/frontend/src/features/chat/components/ChatList.svelte b/frontend/src/features/chat/components/ChatList.svelte
index d0d5d207..63292b97 100644
--- a/frontend/src/features/chat/components/ChatList.svelte
+++ b/frontend/src/features/chat/components/ChatList.svelte
@@ -8,7 +8,7 @@
characterId: string;
};
- let { characterId }: Props = $props();
+ const { characterId }: Props = $props();
let chats = $derived(chatStore.chats.filter((c) => c.characterId === characterId));
let activeChatId = $derived(chatStore.activeChatId);
diff --git a/frontend/src/features/chat/stores/chatStore.svelte.ts b/frontend/src/features/chat/stores/chatStore.svelte.ts
index 0b4e8d9c..6dc4b375 100644
--- a/frontend/src/features/chat/stores/chatStore.svelte.ts
+++ b/frontend/src/features/chat/stores/chatStore.svelte.ts
@@ -195,7 +195,7 @@ export class ChatStore {
async setProvider(
type: T,
- settings: CommonChatSettings & ProviderSettings[T]
+ providerSettings: CommonChatSettings & ProviderSettings[T]
) {
if (this.activeProvider) {
await this.activeProvider.disconnect();
@@ -203,28 +203,29 @@ export class ChatStore {
Logger.structured("llm.request.start", {
provider: type,
- model: settings.model || "default", // Should check if model is in settings type
+ model: providerSettings.model || "default", // Should check if model is in settings type
});
switch (type) {
case "ANTHROPIC": {
- this.activeProvider = await AnthropicChatProvider.factory.connect(settings);
+ this.activeProvider = await AnthropicChatProvider.factory.connect(providerSettings);
break;
}
case "GEMINI": {
- this.activeProvider = await GeminiChatProvider.factory.connect(settings);
+ this.activeProvider = await GeminiChatProvider.factory.connect(providerSettings);
break;
}
case "MOCK": {
- this.activeProvider = await MockChatProvider.factory.connect(settings);
+ this.activeProvider = await MockChatProvider.factory.connect(providerSettings);
break;
}
case "OPENAI": {
- this.activeProvider = await OpenAIChatProvider.factory.connect(settings);
+ this.activeProvider = await OpenAIChatProvider.factory.connect(providerSettings);
break;
}
case "OPENROUTER": {
- this.activeProvider = await OpenRouterChatProvider.factory.connect(settings);
+ this.activeProvider =
+ await OpenRouterChatProvider.factory.connect(providerSettings);
break;
}
default: {
diff --git a/frontend/src/features/persona/components/PersonaForm.svelte b/frontend/src/features/persona/components/PersonaForm.svelte
index 237315f5..832f47ce 100644
--- a/frontend/src/features/persona/components/PersonaForm.svelte
+++ b/frontend/src/features/persona/components/PersonaForm.svelte
@@ -15,7 +15,7 @@
onCancel: () => void;
};
- let { persona = undefined, onSave, onCancel }: Props = $props();
+ const { persona = undefined, onSave, onCancel }: Props = $props();
let name = $state("");
let description = $state("");
diff --git a/frontend/src/features/persona/components/PersonaList.svelte b/frontend/src/features/persona/components/PersonaList.svelte
index 0cb896f2..78c2f669 100644
--- a/frontend/src/features/persona/components/PersonaList.svelte
+++ b/frontend/src/features/persona/components/PersonaList.svelte
@@ -12,7 +12,7 @@
onEdit: (persona: Persona) => void;
};
- let { onEdit }: Props = $props();
+ const { onEdit }: Props = $props();
function handleDelete(id: string) {
if (confirm("Delete this persona?")) {
diff --git a/frontend/src/features/sns/SNSFeedCard.svelte b/frontend/src/features/sns/SNSFeedCard.svelte
index b198c8df..90cf48a3 100644
--- a/frontend/src/features/sns/SNSFeedCard.svelte
+++ b/frontend/src/features/sns/SNSFeedCard.svelte
@@ -24,7 +24,7 @@
tags?: string[];
};
- let {
+ const {
content = "Hello from the SNS mode!",
author = "User",
timestamp = "1m ago",
diff --git a/frontend/src/lib/adapters/storage/chat/LocalStorageChatAdapter.ts b/frontend/src/lib/adapters/storage/chat/LocalStorageChatAdapter.ts
index 8a1eb0e2..b072d911 100644
--- a/frontend/src/lib/adapters/storage/chat/LocalStorageChatAdapter.ts
+++ b/frontend/src/lib/adapters/storage/chat/LocalStorageChatAdapter.ts
@@ -234,7 +234,7 @@ export class LocalStorageChatAdapter implements IChatStorageAdapter {
}
} catch (e) {
Logger.error("Failed to import data", e);
- throw new Error("Invalid data format");
+ throw new Error("Invalid data format", { cause: e });
}
}
}
diff --git a/frontend/src/lib/interfaces/IHookSystem.ts b/frontend/src/lib/interfaces/IHookSystem.ts
index a23c9f99..5322a51c 100644
--- a/frontend/src/lib/interfaces/IHookSystem.ts
+++ b/frontend/src/lib/interfaces/IHookSystem.ts
@@ -5,7 +5,7 @@
*/
export type ScriptContext = {
// Script can access properties dynamically
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ // oxlint-disable-next-line typescript/no-explicit-any
[key: string]: any;
};
diff --git a/frontend/src/lib/providers/chat/LangChainBaseProvider.ts b/frontend/src/lib/providers/chat/LangChainBaseProvider.ts
index f691d2e6..7fe3efa8 100644
--- a/frontend/src/lib/providers/chat/LangChainBaseProvider.ts
+++ b/frontend/src/lib/providers/chat/LangChainBaseProvider.ts
@@ -37,6 +37,7 @@ export abstract class LangChainBaseProvider<
latencyMs: Date.now() - startTime,
});
if (typeof content === "string") return content;
+ // oxlint-disable-next-line eqeqeq
if (content == null) return "";
try {
return JSON.stringify(content);
diff --git a/frontend/src/lib/services/HookService.ts b/frontend/src/lib/services/HookService.ts
index 53d4dd13..6d19e668 100644
--- a/frontend/src/lib/services/HookService.ts
+++ b/frontend/src/lib/services/HookService.ts
@@ -118,27 +118,22 @@ export class HookService {
}
return await regexWorker.replace(content, pattern, replacement, hook.meta.flag);
- } else {
- // String replacement
- if (hook.meta.isOutputScripted) {
- const scriptResult = await scriptingWorker.execute(replacement, {
- context: this.createContext(content, character, type, persona, role),
- allowNetwork,
- characterId: character.id,
- });
- if (scriptResult.result !== undefined && scriptResult.result !== null) {
- replacement = String(scriptResult.result as unknown);
- }
- }
+ }
- const flags = hook.meta.caseSensitive ? "g" : "gi";
- return await regexWorker.replace(
- content,
- this.escapeRegExp(pattern),
- replacement,
- flags
- );
+ // String replacement
+ if (hook.meta.isOutputScripted) {
+ const scriptResult = await scriptingWorker.execute(replacement, {
+ context: this.createContext(content, character, type, persona, role),
+ allowNetwork,
+ characterId: character.id,
+ });
+ if (scriptResult.result !== undefined && scriptResult.result !== null) {
+ replacement = String(scriptResult.result as unknown);
+ }
}
+
+ const flags = hook.meta.caseSensitive ? "g" : "gi";
+ return await regexWorker.replace(content, this.escapeRegExp(pattern), replacement, flags);
}
private createContext(
diff --git a/frontend/src/lib/types/IDataModel.ts b/frontend/src/lib/types/IDataModel.ts
index 8d01d24f..923c8074 100644
--- a/frontend/src/lib/types/IDataModel.ts
+++ b/frontend/src/lib/types/IDataModel.ts
@@ -56,7 +56,7 @@ const BaseLLMConfigSchema = z.object({
/**
* Unique identifier for the LLM configuration.
*/
- id: z.string().default(() => "LLMconfig-" + crypto.randomUUID()),
+ id: z.string().default(() => `LLMconfig-${crypto.randomUUID()}`),
/**
* Human-readable name for the LLM configuration.
* Just for easier identification.
diff --git a/frontend/src/lib/workers/workerClient.ts b/frontend/src/lib/workers/workerClient.ts
index 75014bb8..04948e6e 100644
--- a/frontend/src/lib/workers/workerClient.ts
+++ b/frontend/src/lib/workers/workerClient.ts
@@ -29,8 +29,9 @@ function createWorkerApi(worker: Worker): WorkerApi {
// Automatically set up logging if the worker supports it
if ("setLogReceiver" in api && typeof api.setLogReceiver === "function") {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
- void api.setLogReceiver(Comlink.proxy(logReceiver));
+ void (api.setLogReceiver as (receiver: typeof logReceiver) => Promise)(
+ Comlink.proxy(logReceiver)
+ );
}
// Proxy api again
@@ -46,7 +47,7 @@ function createWorkerApi(worker: Worker): WorkerApi {
if (prop === "disabled") {
return false;
}
- if (typeof prop == "symbol") return Reflect.get(target, prop);
+ if (typeof prop === "symbol") return Reflect.get(target, prop);
return target[prop as keyof T];
},
});
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
index 62ff9ec6..4b9b307d 100644
--- a/frontend/src/vite-env.d.ts
+++ b/frontend/src/vite-env.d.ts
@@ -39,5 +39,7 @@ interface SharedArrayBuffer {
readonly [Symbol.toStringTag]: "SharedArrayBuffer";
}
type ArrayBufferLike = ArrayBuffer;
-// eslint-disable-next-line no-var
+
+// lib.dom.d.ts declared as var
+// oxlint-disable-next-line no-var
declare var SharedArrayBuffer: never;
diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js
index 5ac18645..c45c4bda 100644
--- a/frontend/svelte.config.js
+++ b/frontend/svelte.config.js
@@ -1,15 +1,15 @@
-import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
-
-/** @type {import('@sveltejs/vite-plugin-svelte').SvelteConfig}*/
-export default {
- // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
- // for more information about preprocessors
- preprocess: vitePreprocess(),
- vitePlugin: {
- inspector: {
- showToggleButton: "always",
- toggleButtonPos: "bottom-right",
- },
- },
- compilerOptions: { runes: true },
-};
+import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
+
+/** @type {import('@sveltejs/vite-plugin-svelte').SvelteConfig}*/
+export default {
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+ vitePlugin: {
+ inspector: {
+ showToggleButton: "always",
+ toggleButtonPos: "bottom-right",
+ },
+ },
+ compilerOptions: { runes: true },
+};
diff --git a/frontend/test/browser/features/character/CharacterHooksSettings.test.ts b/frontend/test/browser/features/character/CharacterHooksSettings.test.ts
index 464624d5..f8b17e0f 100644
--- a/frontend/test/browser/features/character/CharacterHooksSettings.test.ts
+++ b/frontend/test/browser/features/character/CharacterHooksSettings.test.ts
@@ -82,17 +82,17 @@ describe("CharacterHooksSettings Component", () => {
await expect.element(memInput).toBeVisible();
await memInput.fill("1024");
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
expect(onChangeSpy).toHaveBeenCalledWith(
expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
executables: expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
runtimeSetting: expect.objectContaining({
mem: 1024,
}),
}),
})
);
- /* eslint-enable @typescript-eslint/no-unsafe-assignment */
});
test("switches between hook tabs", async () => {
@@ -161,11 +161,14 @@ describe("CharacterHooksSettings Component", () => {
const inputField = getByLabelText("Input Pattern");
await expect.element(inputField).toBeVisible();
await inputField.fill("baz");
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
+
expect(onChangeSpy).toHaveBeenCalledWith(
expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
executables: expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
replaceHooks: expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
display: expect.arrayContaining([
expect.objectContaining({ input: "baz" } as const),
]),
@@ -173,7 +176,6 @@ describe("CharacterHooksSettings Component", () => {
}),
})
);
- /* eslint-enable @typescript-eslint/no-unsafe-assignment */
});
test("deletes a hook", async () => {
@@ -204,13 +206,13 @@ describe("CharacterHooksSettings Component", () => {
expect(onChangeSpy).toHaveBeenCalledWith(
expect.objectContaining({
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
executables: expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
replaceHooks: expect.objectContaining({
display: [],
}),
}),
- /* eslint-enable @typescript-eslint/no-unsafe-assignment */
})
);
});
@@ -248,17 +250,19 @@ describe("CharacterHooksSettings Component", () => {
expect(onChangeSpy).toHaveBeenCalledWith(
expect.objectContaining({
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
executables: expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
replaceHooks: expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
display: expect.arrayContaining([
expect.objectContaining({
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
meta: expect.objectContaining({ type: "regex" }),
}),
]),
}),
}),
- /* eslint-enable @typescript-eslint/no-unsafe-assignment */
})
);
});
diff --git a/frontend/test/browser/features/character/CharacterList.test.ts b/frontend/test/browser/features/character/CharacterList.test.ts
index b38b633c..bb91f5c2 100644
--- a/frontend/test/browser/features/character/CharacterList.test.ts
+++ b/frontend/test/browser/features/character/CharacterList.test.ts
@@ -17,7 +17,7 @@ vi.mock("@/features/character/adapters/assetStorage/OpFSAssetStorageAdapter", ()
getAssetUrl: vi.fn().mockResolvedValue(null),
};
return {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ // oxlint-disable-next-line typescript/no-explicit-any
OpFSAssetStorageAdapter: vi.fn().mockImplementation(function (this: any) {
return mockAdapter;
}),
@@ -29,7 +29,7 @@ vi.mock(
"comlink",
() =>
({
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+ // oxlint-disable-next-line typescript/no-unsafe-return
transfer: vi.fn((data, _transferables) => data) satisfies (typeof comlink)["transfer"],
}) satisfies Partial
);
diff --git a/frontend/test/browser/features/persona/PersonaList.test.ts b/frontend/test/browser/features/persona/PersonaList.test.ts
index 61ce4668..52e4ec6d 100644
--- a/frontend/test/browser/features/persona/PersonaList.test.ts
+++ b/frontend/test/browser/features/persona/PersonaList.test.ts
@@ -11,7 +11,7 @@ vi.mock("@/features/character/adapters/assetStorage/OpFSAssetStorageAdapter", ()
getAssetUrl: vi.fn().mockResolvedValue(null),
};
return {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ // oxlint-disable-next-line typescript/no-explicit-any
OpFSAssetStorageAdapter: vi.fn().mockImplementation(function (this: any) {
return mockAdapter;
}),
diff --git a/frontend/test/browser/features/settings/GenerationParameters.test.ts b/frontend/test/browser/features/settings/GenerationParameters.test.ts
index aba88758..45690740 100644
--- a/frontend/test/browser/features/settings/GenerationParameters.test.ts
+++ b/frontend/test/browser/features/settings/GenerationParameters.test.ts
@@ -1,7 +1,7 @@
///
import { test, expect, describe, vi, beforeEach } from "vitest";
import { render } from "vitest-browser-svelte";
-import Wrapper from "../../wrappers/GenerationParametersTestWrapper.svelte";
+import Wrapper from "@test/browser/wrappers/GenerationParametersTestWrapper.svelte";
import { settings } from "@/lib/stores/settings.svelte";
import type { LLMConfig } from "@/lib/types/IDataModel";
diff --git a/frontend/test/browser/integration/ChatPersion.test.ts b/frontend/test/browser/integration/ChatPersion.test.ts
index 2c0330da..179e7c3f 100644
--- a/frontend/test/browser/integration/ChatPersion.test.ts
+++ b/frontend/test/browser/integration/ChatPersion.test.ts
@@ -27,8 +27,9 @@ describe("Persona and Chat interactions", () => {
// Mock chatStore.waitForSettings to avoid delays
const { chatStore } = await import("@/features/chat/stores/chatStore.svelte");
+
// Cast chatStore to access all private members
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ // oxlint-disable-next-line typescript/no-explicit-any
vi.spyOn(chatStore as any, "waitForSettings").mockResolvedValue(undefined);
await chatStore.initPromise;
await chatStore.setProvider("MOCK", {
diff --git a/frontend/test/browser/wrappers/ButtonTestWrapper.svelte b/frontend/test/browser/wrappers/ButtonTestWrapper.svelte
index 4fe25bf3..8f7c25f4 100644
--- a/frontend/test/browser/wrappers/ButtonTestWrapper.svelte
+++ b/frontend/test/browser/wrappers/ButtonTestWrapper.svelte
@@ -10,7 +10,7 @@
label: string;
}
- let { variant, size, disabled, type, onclick, label }: Props = $props();
+ const { variant, size, disabled, type, onclick, label }: Props = $props();