Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8db9dc6
refactor(AGENTS.md): clarify DaisyUI usage and update testing library…
concertypin Jan 14, 2026
e9cf0df
feat(ui): implement character settings modal with autosave functionality
concertypin Jan 16, 2026
905a370
refactor(deps): update tsconfig settings for module resolution and ty…
concertypin Jan 16, 2026
9322b9a
test(browser): remove .concurrent from describe blocks in browser tests
concertypin Jan 16, 2026
6230ee3
chore(ci): remove redundant json-summary reporter from CI coverage check
concertypin Jan 16, 2026
686b5ba
Merge branch 'kei' of https://github.com/concertypin/ArisuTalk into k…
concertypin Jan 16, 2026
1f44f6e
test(telemetry): update PromiseRejectionEvent reason type in telemetr…
concertypin Jan 16, 2026
91f5443
refactor(settings): update Prettier extension ID in VS Code settings
concertypin Jan 18, 2026
7031b37
refactor(chatStore): explicitly type lastMsg variable for clarity
concertypin Jan 18, 2026
9f3c869
refactor(tests): replace deprecated `asMock` utility with `vi.mocked`…
concertypin Jan 18, 2026
0cac1db
refactor(test): simplify worker mocking in characterStore tests
concertypin Jan 18, 2026
faed444
refactor(deps): remove unused ts-deepmerge dependency
concertypin Jan 19, 2026
16006c1
refactor(deps): replace structuredClone with lodash-es/cloneDeep for …
concertypin Jan 19, 2026
e7d997f
feat: small animation
concertypin Jan 20, 2026
60b3bfa
refactor(deps): remove unused devDependencies and update node version…
concertypin Jan 21, 2026
eaad59c
feat(assets): implement character asset management UI and logic
concertypin Jan 21, 2026
6fe8d75
refactor(deps): replace Lucide icons with Phosphor icons in frontend
concertypin Jan 22, 2026
3c263a4
feat(debug): remove debug export scripts and temporary files
concertypin Jan 22, 2026
ec7adf3
feat(deps): upgrade @arisutalk/character-spec to v0.0.17
concertypin Jan 23, 2026
9752f70
fix: remove sveltePhosphorOptimize which makes bug on build
concertypin Jan 24, 2026
3b4c43a
fix: suggestion from gemini
concertypin Jan 24, 2026
9e04aec
refactor(ui): update color palette in global.css for better contrast …
concertypin Jan 24, 2026
e7ce8b5
feat(settings): implement customizable font family and size settings
concertypin Jan 28, 2026
25a82c7
feat(components): add NoopIcon component and integrate into settings …
concertypin Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Run coverage check
run: pnpm run --filter frontend test:coverage --coverage.reporter=json-summary
run: pnpm run --filter frontend test:coverage
timeout-minutes: 5

- name: Update Coverage Status
Expand Down
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"recommendations": [
"svelte.svelte-vscode",
"dbaeumer.vscode-eslint",
"prettier.prettier-vscode"
"esbenp.prettier-vscode",
"vitest.explorer"
]
}
29 changes: 0 additions & 29 deletions .vscode/launch.json

This file was deleted.

18 changes: 12 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"[javascript]": {
"editor.defaultFormatter": "prettier.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 4
},
"[typescript]": {
"editor.defaultFormatter": "prettier.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 4
},
"[svelte]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "prettier.prettier-vscode"
"editor.defaultFormatter": "svelte.svelte-vscode"
},
"[json]": {
"editor.defaultFormatter": "prettier.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 4
},
"typescript.preferences.preferTypeOnlyAutoImports": true,
Expand All @@ -24,6 +24,12 @@
"vitest.nodeEnv": {
// Enabling browser test on VSC vitest,
// since it will not run browser tests by default
"npm_lifecycle_event": "coverage"
}
"npm_lifecycle_event": "coverage",
"IS_IDE": "vscode"
},
"eslint.workingDirectories": [
{
"directory": "./frontend/",
}
]
}
3 changes: 2 additions & 1 deletion docs/rule/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ There are some rules for readability! Check out the following documents:

- [TypeScript](./typescript.md)
- [Svelte](./svelte.md)
- [Testing](./tests.md)
- [Testing](./tests.md)
- [Regional rules](./regional.md)
14 changes: 14 additions & 0 deletions docs/rule/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ Test should cover:
- unexpected states
- TypeScript's type check, via Vitest's type assertion features. See [more](https://vitest.dev/guide/testing-types) and [more](https://github.com/mmkal/expect-type)

### Guidelines:

When using mocks, prefer `vi.mocked()` over direct casting to `any` for better type safety.
```typescript
import { vi } from 'vitest';
const myMockedFunction = vi.fn();

// Don't(it makes error on lint. Also, loses type safety and autocomplete.)
(myMockedFunction as any).mockReturnValue(42);

// Do:
vi.mocked(myMockedFunction).mockReturnValue(42);
```

## 1. General Logic Tests (Node.js)

Use these for your `.svelte.ts` files or pure TypeScript utility files. These tests run in Node.js for maximum speed. Since Svelte 5 runes are "universal," they can be tested without a browser if they don't touch the DOM.
Expand Down
4 changes: 3 additions & 1 deletion frontend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

- **JSDoc Documentation**: Use JSDoc for all exported functions and complex logic.
- HTML Separation: Avoid HTML strings in JS files. Who does that with Svelte?
- Use DaisyUI: We have libraries, use them!
- Use DaisyUI: We have libraries, use them! Don't use Tailwind CSS's classes if DaisyUI has the same class.
- Use color theme, not Tailwind CSS's color classes.

### Event Handling

Expand Down Expand Up @@ -91,6 +92,7 @@ Worker logic should be tested via unit tests in `test/workers/`. Note that real
- Using `lucide-svelte` instead of `@lucide/svelte`.
- Fix: Replace `lucide-svelte` with `@lucide/svelte`.
- Using `@testing-library/svelte` instead of `vitest-browser-svelte`.
- This makes tests run in not browser, but in Node.js. It might not work as intended, since some are not the same as in the browser.
- Fix: Replace `@testing-library/svelte` with `vitest-browser-svelte`.

## Documentation of Other Libraries
Expand Down
2 changes: 2 additions & 0 deletions frontend/common/logger/LogBridge.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// It's logger, so we should use console
/* eslint-disable no-console */
import type { StandardLogEntry, LogLevel, StructuredLogEntry } from "./Logger";
import type { StructuredLogLevel } from "./LogType";

Expand Down
9 changes: 9 additions & 0 deletions frontend/common/logger/LogType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type StructuredLogUI = {
location: string;
/** Modal identifier (e.g., "LLMSettings") */
modalName: string;
/** Optional character context */
characterId?: string;
};
/**
* Modal dialog closed.
Expand Down Expand Up @@ -112,6 +114,13 @@ type StructuredLogCharacter = {
/** Error message if failed */
errorMessage?: string;
};
/**
* Character settings autosaved.
*/
"character.autosave": {
/** Character ID */
characterId: string;
};
};
type StructuredLogLLM = {
// ─── LLM Events ─────────────────────────────────────────────
Expand Down
10 changes: 10 additions & 0 deletions frontend/common/logger/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,16 @@ export class Logger {
this.listeners.delete(listener);
}

/**
* Reset the logger to its initial state.
* Useful for clearing all listeners and resetting the log level in tests.
*/
static reset(): void {
this.listeners.clear();
currentLogLevel = getStoredLogLevel();
initializeAdze(currentLogLevel);
}

/**
* Remove all listeners.
*/
Expand Down
14 changes: 10 additions & 4 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import globals from "globals";
import { defineConfig } from "eslint/config";
import { dirname } from "path";
import { fileURLToPath } from "url";
import phosphorSvelte from "eslint-plugin-phosphor-svelte";

const __dirname = dirname(fileURLToPath(import.meta.url));

const tsConfig = !process.env.SKIP_TYPE_LINT
? ts.configs.recommendedTypeChecked
: ts.configs.recommended;

const isCI = process.env.CI ? true : false;
export default defineConfig([
{
ignores: ["dist/", "node_modules/", "*.config.*"],
},
js.configs.recommended,
...tsConfig,
...svelte.configs["flat/recommended"],
phosphorSvelte.configs.recommended,
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parserOptions: {
projectService: true,
Expand Down Expand Up @@ -49,9 +55,9 @@ export default defineConfig([
"@typescript-eslint/require-await": "off",
// Disable unbound-method - false positives with Svelte stores
"@typescript-eslint/unbound-method": "off",
"no-debugger": isCI ? "error" : "warn",
// Use dedicated logger. Console is unrecommended since it's not pretty
"no-console": "warn",
},
},
{
ignores: ["dist/", "node_modules/", "*.config.*"],
},
]);
14 changes: 8 additions & 6 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test:coverage": "vitest run test --coverage",
"check": "pnpm run format && pnpm run lint:fix && concurrently --group \"pnpm run test\" \"pnpm run test:browser\"",
"check:types": "concurrently --group \"svelte-check --tsconfig tsconfig.app.json --fail-on-warnings\" \"svelte-check --tsconfig tsconfig.worker.json --fail-on-warnings\" \"svelte-check --tsconfig tsconfig.test.json\"",
"lint": "pnpm run check:types && eslint --cache --cache-location ./node_modules/.cache/eslint/ src/ test/ worker/",
"lint": "pnpm run check:types && eslint --cache --cache-location ./node_modules/.cache/eslint/ src/ test/ worker/ common/",
"lint:fix": "pnpm run lint --fix",
"format": "pnpm run format:check --write --list-different",
"format:check": "prettier --cache --cache-location ./node_modules/.cache/prettier/ src/ test/ worker/"
Expand All @@ -29,25 +29,27 @@
"node": ">20"
},
"dependencies": {
"@arisutalk/character-spec": "0.0.16",
"@arisutalk/character-spec": "0.0.17",
"@humanspeak/svelte-markdown": "^0.8.13",
"@langchain/anthropic": "^1.3.3",
"@langchain/core": "^1.1.4",
"@langchain/google-genai": "^2.1.0",
"@langchain/openai": "^1.2.0",
"@lucide/svelte": "^0.559.0",
"adze": "^2.2.5",
"cbor-x": "^1.6.0",
"comlink": "^4.4.2",
"define-function": "^2.0.0",
"dexie": "^4.2.1",
"lodash-es": "^4.17.22",
"phosphor-svelte": "^3.0.1",
"svelte": "^5.39.3",
"zod": "^4.1.12"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/postcss": "^4.1.18",
"@types/lodash-es": "^4.17.12",
"@vitest/browser": "^4.0.7",
"@vitest/browser-playwright": "^4.0.7",
"@vitest/coverage-v8": "^4.0.7",
Expand All @@ -56,20 +58,20 @@
"concurrently": "^9.2.1",
"daisyui": "^5.5.13",
"eslint": "^9.39.1",
"eslint-plugin-phosphor-svelte": "^0.0.2",
"eslint-plugin-svelte": "^3.13.1",
"fake-indexeddb": "^6.2.5",
"globals": "^16.5.0",
"happy-dom": "^20.0.10",
"playwright": "^1.57.0",
"postcss": "^8.5.6",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.0",
"prettier": "^3.8.0",
"prettier-plugin-svelte": "^3.4.1",
"svelte-check": "^4.3.4",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0",
"vite": "^7.1.2",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.7",
"vitest-browser-svelte": "^2.0.1"
}
Expand Down
72 changes: 49 additions & 23 deletions frontend/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
-->
<script lang="ts">
import { initRouter, getCurrentPath } from "@/lib/router.svelte";
import { Logger } from "@common/logger/Logger";
import { routes } from "@/lib/routeConfig";
import { uiState } from "@/lib/stores/ui.svelte";
import { settings } from "@/lib/stores/settings.svelte";
import ToastContainer from "@/components/ToastContainer.svelte";
import IconContext from "phosphor-svelte/lib/IconContext";
import type { Component } from "svelte";
import { loadFont } from "@/lib/utils/fontUtils";

// Initialize router and settings on mount
$effect(() => {
Expand All @@ -35,36 +38,59 @@
} finally {
isLoading = false;
}
})().catch((err) => console.error("Failed to load route", err));
})().catch((err) => Logger.error("Failed to load route", err));
});

// Apply global font settings
$effect(() => {
const { fontSize, fontFamily } = settings.value;
if (fontFamily) {
loadFont(fontFamily);
document.documentElement.style.setProperty("--app-font-family", fontFamily);
}
if (fontSize) {
document.documentElement.style.setProperty("--app-font-size", `${fontSize}px`);
}
});
</script>

<svelte:head>
<title>ArisuTalk</title>
</svelte:head>

{#if isLoading}
<div class="flex items-center justify-center w-full h-full text-base-content/50">
Loading...
</div>
{:else if CurrentComponent}
<CurrentComponent />
{/if}

{#if uiState.settingsModalOpen}
{#await import("@/components/SettingsModal.svelte")}
<IconContext values={{ weight: "bold", size: 24, mirrored: false }}>
{#if isLoading}
<div class="flex items-center justify-center w-full h-full text-base-content/50">
Loading settings...
</div>
{:then { default: Component }}
<Component />
{:catch error}
<div
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 text-white"
>
Failed to load settings modal: {String(error)}
Loading...
</div>
{/await}
{/if}
{:else if CurrentComponent}
<CurrentComponent />
{/if}

{#if uiState.settingsModalOpen}
{#await import("@/components/SettingsModal.svelte")}
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 text-base-content/70 backdrop-blur-sm"
>
<span class="loading loading-spinner loading-lg"></span>
</div>
{:then { default: Component }}
<Component />
{:catch error}
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 text-error backdrop-blur-sm"
>
<div class="bg-100 p-8 rounded-xl shadow-xl border border-error/20">
<h3 class="font-bold text-lg mb-2">Error Loading Settings</h3>
<p>{String(error)}</p>
<button
class="btn btn-sm btn-ghost mt-4"
onclick={() => uiState.closeSettingsModal()}>Close</button
>
</div>
</div>
{/await}
{/if}

<ToastContainer />
<ToastContainer />
</IconContext>
Loading