diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..584788c0
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,26 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+
+jobs:
+ frontend-quality:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v2
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Typecheck app + web
+ run: bun run typecheck
+
+ - name: Build web smoke test
+ run: bun run build:web
diff --git a/SECURITY.md b/SECURITY.md
index bdf1e917..049dd778 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,8 +6,8 @@ We release patches for security vulnerabilities. Which versions are eligible for
| Version | Supported |
| ------- | ------------------ |
-| 0.1.x | :white_check_mark: |
-| < 0.1 | :x: |
+| 0.3.x | :white_check_mark: |
+| < 0.3 | :x: |
## Reporting a Vulnerability
@@ -82,7 +82,7 @@ Timeline may vary based on severity and complexity.
## Security Updates
Security updates will be:
-- Released as patch versions (e.g., 0.1.1)
+- Released as patch versions (e.g., 0.3.2)
- Documented in CHANGELOG.md
- Announced via GitHub releases
- Automatically delivered via auto-updater
diff --git a/app/package.json b/app/package.json
index 57e41627..c10654e8 100644
--- a/app/package.json
+++ b/app/package.json
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "vite build",
+ "typecheck": "tsc -p tsconfig.json --noEmit",
"preview": "vite preview",
"lint": "biome lint src",
"lint:fix": "biome lint --write src",
diff --git a/app/src/App.tsx b/app/src/App.tsx
index e77552aa..d277c802 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -121,7 +121,6 @@ function App() {
console.log('Dev mode: Skipping auto-start of server (run it separately)');
setServerReady(true); // Mark as ready so UI doesn't show loading screen
// Mark that server was not started by app (so we don't try to stop it on close)
- // @ts-expect-error - adding property to window
window.__voiceboxServerStartedByApp = false;
return;
}
@@ -144,13 +143,11 @@ function App() {
useServerStore.getState().setServerUrl(serverUrl);
setServerReady(true);
// Mark that we started the server (so we know to stop it on close)
- // @ts-expect-error - adding property to window
window.__voiceboxServerStartedByApp = true;
})
.catch((error) => {
console.error('Failed to auto-start server:', error);
serverStartingRef.current = false;
- // @ts-expect-error - adding property to window
window.__voiceboxServerStartedByApp = false;
// Only fall back to health-check polling when the error indicates the
diff --git a/app/src/components/AudioTab/AudioTab.tsx b/app/src/components/AudioTab/AudioTab.tsx
index 44058bc5..5df94f0b 100644
--- a/app/src/components/AudioTab/AudioTab.tsx
+++ b/app/src/components/AudioTab/AudioTab.tsx
@@ -124,7 +124,7 @@ export function AudioTab() {
);
}
- const handleChannelDelete = async (e, channelId) => {
+ const handleChannelDelete = async (e: React.MouseEvent, channelId: string) => {
e.stopPropagation();
if (await confirm('Delete this channel?')) {
deleteChannel.mutate(channelId);
diff --git a/app/src/components/ServerSettings/ModelManagement.tsx b/app/src/components/ServerSettings/ModelManagement.tsx
index 9893984d..4fc72321 100644
--- a/app/src/components/ServerSettings/ModelManagement.tsx
+++ b/app/src/components/ServerSettings/ModelManagement.tsx
@@ -1076,105 +1076,3 @@ export function ModelManagement() {
);
}
-interface ModelItemProps {
- model: {
- model_name: string;
- display_name: string;
- downloaded: boolean;
- downloading?: boolean; // From server - true if download in progress
- size_mb?: number;
- loaded: boolean;
- };
- onDownload: () => void;
- onDelete: () => void;
- isDownloading: boolean; // Local state - true if user just clicked download
- formatSize: (sizeMb?: number) => string;
-}
-
-function ModelItem({ model, onDownload, onDelete, isDownloading, formatSize }: ModelItemProps) {
- // Use server's downloading state OR local state (for immediate feedback before server updates)
- const showDownloading = model.downloading || isDownloading;
-
- const statusText = model.loaded
- ? 'Loaded'
- : showDownloading
- ? 'Downloading'
- : model.downloaded
- ? 'Downloaded'
- : 'Not downloaded';
- const sizeText =
- model.downloaded && model.size_mb && !showDownloading ? `, ${formatSize(model.size_mb)}` : '';
- const rowLabel = `${model.display_name}, ${statusText}${sizeText}. Use Tab to reach Download or Delete.`;
-
- return (
-
-
-
- {model.display_name}
- {model.loaded && (
-
- Loaded
-
- )}
- {/* Only show Downloaded if actually downloaded AND not downloading */}
- {model.downloaded && !model.loaded && !showDownloading && (
-
- Downloaded
-
- )}
-
- {model.downloaded && model.size_mb && !showDownloading && (
-
- Size: {formatSize(model.size_mb)}
-
- )}
-
-
- {model.downloaded && !showDownloading ? (
-
- ) : showDownloading ? (
-
- ) : (
-
- )}
-
-
- );
-}
diff --git a/app/src/components/StoriesTab/StoryTrackEditor.tsx b/app/src/components/StoriesTab/StoryTrackEditor.tsx
index 641ad048..96cbcef1 100644
--- a/app/src/components/StoriesTab/StoryTrackEditor.tsx
+++ b/app/src/components/StoriesTab/StoryTrackEditor.tsx
@@ -371,7 +371,7 @@ export function StoryTrackEditor({ storyId, items }: StoryTrackEditorProps) {
}
}, [isResizing, handleResizeMove, handleResizeEnd]);
- const handleTimelineClick = (e: React.MouseEvent) => {
+ const handleTimelineClick = (e: React.MouseEvent) => {
if (!tracksRef.current || draggingItem || trimmingItem) return;
const rect = tracksRef.current.getBoundingClientRect();
const x = e.clientX - rect.left + tracksRef.current.scrollLeft;
diff --git a/app/src/hooks/useAutoUpdater.ts b/app/src/hooks/useAutoUpdater.ts
index 7a9f169a..b46764c8 100644
--- a/app/src/hooks/useAutoUpdater.ts
+++ b/app/src/hooks/useAutoUpdater.ts
@@ -5,7 +5,15 @@ import type { UpdateStatus } from '@/platform/types';
// Re-export UpdateStatus for backwards compatibility
export type { UpdateStatus };
-export function useAutoUpdater(checkOnMount = false) {
+interface UseAutoUpdaterOptions {
+ checkOnMount?: boolean;
+ showToast?: boolean;
+}
+
+export function useAutoUpdater(options: boolean | UseAutoUpdaterOptions = false) {
+ const { checkOnMount } =
+ typeof options === 'boolean' ? { checkOnMount: options } : { checkOnMount: options.checkOnMount ?? false };
+
const platform = usePlatform();
const [status, setStatus] = useState(platform.updater.getStatus());
const hasCheckedRef = useRef(false);
@@ -38,10 +46,11 @@ export function useAutoUpdater(checkOnMount = false) {
useEffect(() => {
if (checkOnMount && platform.metadata.isTauri && !hasCheckedRef.current) {
hasCheckedRef.current = true;
- checkForUpdates();
+ checkForUpdates().catch((error) => {
+ console.error('Auto update check failed:', error);
+ });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [platform.metadata.isTauricheckOnMountcheckForUpdates]);
+ }, [checkOnMount, checkForUpdates, platform.metadata.isTauri]);
return {
status,
diff --git a/app/src/hooks/useAutoUpdater.tsx b/app/src/hooks/useAutoUpdater.tsx
index 8a6351f6..32c86118 100644
--- a/app/src/hooks/useAutoUpdater.tsx
+++ b/app/src/hooks/useAutoUpdater.tsx
@@ -73,7 +73,7 @@ export function useAutoUpdater(options: boolean | UseAutoUpdaterOptions = false)
}
// Empty dependency array - only run once on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [platform.metadata.isTauricheckOnMountcheckForUpdates]);
+ }, [checkOnMount, checkForUpdates, platform.metadata.isTauri]);
// Show toast when update is available
useEffect(() => {
diff --git a/package.json b/package.json
index 247e37e8..bf3cb741 100644
--- a/package.json
+++ b/package.json
@@ -24,12 +24,13 @@
"update:icons": "./scripts/update-icons.sh",
"convert:assets": "./scripts/convert-assets.sh",
"lint": "biome lint .",
+ "typecheck": "bunx tsc -p app/tsconfig.json --noEmit && cd web && bunx tsc --noEmit",
"lint:fix": "biome lint --write .",
"format": "biome format --write .",
"format:check": "biome format .",
"check": "biome check .",
"check:fix": "biome check --write .",
- "ci": "biome ci ."
+ "ci": "bun run typecheck && bun run build:web"
},
"devDependencies": {
"@biomejs/biome": "2.3.12",
diff --git a/web/src/platform/updater.ts b/web/src/platform/updater.ts
index 32ed0148..2f8be445 100644
--- a/web/src/platform/updater.ts
+++ b/web/src/platform/updater.ts
@@ -11,10 +11,6 @@ class WebUpdater implements PlatformUpdater {
private subscribers: Set<(status: UpdateStatus) => void> = new Set();
- private notifySubscribers() {
- this.subscribers.forEach((callback) => callback(this.status));
- }
-
subscribe(callback: (status: UpdateStatus) => void): () => void {
this.subscribers.add(callback);
callback(this.status);
diff --git a/web/tsconfig.json b/web/tsconfig.json
index 629bcb7f..476a26b1 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -18,8 +18,9 @@
"baseUrl": ".",
"paths": {
"@/*": ["../app/src/*"]
- }
+ },
+ "types": ["vite/client"]
},
- "include": ["src"],
+ "include": ["src", "../app/src/global.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}