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 ? ( -
-
- Ready -
- -
- ) : 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" }] }