From 5caafa95d637ba19c51487c75b4a44f0389ba113 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:52:07 +1000 Subject: [PATCH] fix: address CodeRabbit review feedback (round 2) - Gate audio level meter to selected mic device only (helperComponents) - Clear webcam deviceId when 'Default' selected (LaunchWindow) - Wire i18n for 'Custom Fonts' and 'Color' labels (AnnotationTextTab) - Restore keyboard focus ring on sidebar buttons (EditorSidebar) - Add visually-hidden DialogTitle for a11y (ExtensionDetailModal) - Make extension cards keyboard-accessible with role/tabIndex (ExtensionManagerCards) - Write smoke export reports for GIF exports before closing (editorExportWorkflow) - Guard GIF export against zero video dimensions (useEditorExport) - Allow 'Save Again' before requiring loaded video (useEditorExport) --- .../launch/LaunchWindow/helperComponents.tsx | 2 +- src/components/launch/LaunchWindow/index.tsx | 5 +-- .../video-editor/AnnotationTextTab.tsx | 4 +- src/components/video-editor/EditorSidebar.tsx | 2 +- .../ExtensionDetailModal.tsx | 3 +- .../ExtensionManagerCards.tsx | 10 ++++- .../hooks/editorExportWorkflow.ts | 37 +++++++++++++++++++ .../video-editor/hooks/useEditorExport.ts | 18 +++++---- src/i18n/locales/en/editor.json | 4 +- src/i18n/locales/es/editor.json | 4 +- src/i18n/locales/ko/editor.json | 4 +- src/i18n/locales/nl/editor.json | 4 +- src/i18n/locales/zh-CN/editor.json | 4 +- 13 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/components/launch/LaunchWindow/helperComponents.tsx b/src/components/launch/LaunchWindow/helperComponents.tsx index 8cb1d434..557ae71f 100644 --- a/src/components/launch/LaunchWindow/helperComponents.tsx +++ b/src/components/launch/LaunchWindow/helperComponents.tsx @@ -73,7 +73,7 @@ export function MicDeviceRow({ onSelect: () => void; }) { const { level } = useAudioLevelMeter({ - enabled: true, + enabled: selected, deviceId: device.deviceId, }); diff --git a/src/components/launch/LaunchWindow/index.tsx b/src/components/launch/LaunchWindow/index.tsx index 036a0252..de6a0600 100644 --- a/src/components/launch/LaunchWindow/index.tsx +++ b/src/components/launch/LaunchWindow/index.tsx @@ -126,9 +126,8 @@ export function LaunchWindow() { }, [selectedDeviceId, setMicrophoneDeviceId]); useEffect(() => { - if (selectedVideoDeviceId && selectedVideoDeviceId !== "default") { - setWebcamDeviceId(selectedVideoDeviceId); - } + if (!selectedVideoDeviceId) return; + setWebcamDeviceId(selectedVideoDeviceId === "default" ? undefined : selectedVideoDeviceId); }, [selectedVideoDeviceId, setWebcamDeviceId]); useEffect(() => { diff --git a/src/components/video-editor/AnnotationTextTab.tsx b/src/components/video-editor/AnnotationTextTab.tsx index f2b99587..95f6bbbb 100644 --- a/src/components/video-editor/AnnotationTextTab.tsx +++ b/src/components/video-editor/AnnotationTextTab.tsx @@ -93,7 +93,7 @@ export function AnnotationTextTab({ {customFonts.length > 0 && ( <>
- Custom Fonts + {t("annotations.customFonts")}
{customFonts.map((font) => ( {annotation.style.backgroundColor === "transparent" ? t("annotations.none") - : "Color"} + : t("annotations.color")} diff --git a/src/components/video-editor/EditorSidebar.tsx b/src/components/video-editor/EditorSidebar.tsx index 3fa92ccd..f2a2de80 100644 --- a/src/components/video-editor/EditorSidebar.tsx +++ b/src/components/video-editor/EditorSidebar.tsx @@ -89,7 +89,7 @@ export function EditorSidebar({ type="button" onClick={() => prefs.setActiveEffectSection(section.id)} title={section.label} - className="group relative flex h-9 w-9 items-center justify-center rounded-lg outline-none focus:outline-none focus-visible:outline-none" + className="group relative flex h-9 w-9 items-center justify-center rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-[#2563EB]/50 focus-visible:ring-offset-1" animate={{ opacity: isActive ? 1 : 0.55 }} transition={{ duration: 0.14 }} > diff --git a/src/components/video-editor/extension-manager/ExtensionDetailModal.tsx b/src/components/video-editor/extension-manager/ExtensionDetailModal.tsx index f8391bf6..7c62a279 100644 --- a/src/components/video-editor/extension-manager/ExtensionDetailModal.tsx +++ b/src/components/video-editor/extension-manager/ExtensionDetailModal.tsx @@ -6,7 +6,7 @@ import { Tag, } from "@phosphor-icons/react"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Switch } from "@/components/ui/switch"; import { useScopedT } from "@/contexts/I18nContext"; import { ExtensionIcon } from "../ExtensionIcon"; @@ -41,6 +41,7 @@ export function ExtensionDetailModal({ return ( !open && onClose()}> + {name}
diff --git a/src/components/video-editor/extension-manager/ExtensionManagerCards.tsx b/src/components/video-editor/extension-manager/ExtensionManagerCards.tsx index 18a8fbf0..3f7a7d63 100644 --- a/src/components/video-editor/extension-manager/ExtensionManagerCards.tsx +++ b/src/components/video-editor/extension-manager/ExtensionManagerCards.tsx @@ -35,8 +35,10 @@ export function InstalledExtensionCard({ return (
{ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick?.(); } }} >
{ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick?.(); } }} >
{extension.iconUrl ? ( diff --git a/src/components/video-editor/hooks/editorExportWorkflow.ts b/src/components/video-editor/hooks/editorExportWorkflow.ts index 45d53109..7bac8d4b 100644 --- a/src/components/video-editor/hooks/editorExportWorkflow.ts +++ b/src/components/video-editor/hooks/editorExportWorkflow.ts @@ -183,6 +183,10 @@ export async function runEditorExport({ ) : await window.electronAPI.saveExportedVideo(arrayBuffer, fileName); + const smokeElapsedMs = smokeExportStartedAt !== null + ? Math.round(performance.now() - smokeExportStartedAt) + : undefined; + if (saveResult.canceled) { setPendingExportSave({ arrayBuffer, fileName }); setExportError( @@ -190,6 +194,15 @@ export async function runEditorExport({ ); toast.info("Save canceled. You can save again without re-exporting."); keepExportDialogOpen = true; + if (smokeExportConfig.enabled) { + await writeSmokeExportReport(smokeExportConfig.outputPath, { + success: false, + phase: "save", + format: "gif", + elapsedMs: smokeElapsedMs, + error: "Save canceled", + }); + } } else if (saveResult.success && saveResult.path) { if (smokeExportStartedAt !== null) { console.log( @@ -199,6 +212,13 @@ export async function runEditorExport({ showExportSuccessToast(saveResult.path); setExportedFilePath(saveResult.path); if (smokeExportConfig.enabled) { + await writeSmokeExportReport(smokeExportConfig.outputPath, { + success: true, + phase: "complete", + format: "gif", + elapsedMs: smokeElapsedMs, + outputPath: saveResult.path, + }); window.close(); return; } @@ -206,6 +226,13 @@ export async function runEditorExport({ setExportError(saveResult.message || "Failed to save GIF"); toast.error(saveResult.message || "Failed to save GIF"); if (smokeExportConfig.enabled) { + await writeSmokeExportReport(smokeExportConfig.outputPath, { + success: false, + phase: "save", + format: "gif", + elapsedMs: smokeElapsedMs, + error: saveResult.message || "Failed to save GIF", + }); window.close(); return; } @@ -214,6 +241,16 @@ export async function runEditorExport({ setExportError(result.error || "GIF export failed"); toast.error(result.error || "GIF export failed"); if (smokeExportConfig.enabled) { + const smokeElapsedMs = smokeExportStartedAt !== null + ? Math.round(performance.now() - smokeExportStartedAt) + : undefined; + await writeSmokeExportReport(smokeExportConfig.outputPath, { + success: false, + phase: "export", + format: "gif", + elapsedMs: smokeElapsedMs, + error: result.error || "GIF export failed", + }); window.close(); return; } diff --git a/src/components/video-editor/hooks/useEditorExport.ts b/src/components/video-editor/hooks/useEditorExport.ts index 6ead07a8..06ecf387 100644 --- a/src/components/video-editor/hooks/useEditorExport.ts +++ b/src/components/video-editor/hooks/useEditorExport.ts @@ -127,16 +127,16 @@ export function useEditorExport({ ); const handleOpenExportDropdown = useCallback(() => { - const { videoPath } = getRenderConfig(); - if (!videoPath) { - toast.error("No video loaded"); - return; - } if (hasPendingExportSave) { setShowExportDropdown(true); setExportError("Save dialog canceled. Click Save Again to save without re-rendering."); return; } + const { videoPath } = getRenderConfig(); + if (!videoPath) { + toast.error("No video loaded"); + return; + } setShowExportDropdown(true); setExportProgress(null); setExportError(null); @@ -153,8 +153,12 @@ export function useEditorExport({ toast.error("Video not ready"); return; } - const sourceWidth = video.videoWidth || 1920; - const sourceHeight = video.videoHeight || 1080; + const sourceWidth = video.videoWidth; + const sourceHeight = video.videoHeight; + if (!sourceWidth || !sourceHeight) { + toast.error("Video dimensions not ready. Please wait for the video to load."); + return; + } const gifDimensions = calculateOutputDimensions( sourceWidth, sourceHeight, diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index cf070cde..93443f96 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -42,7 +42,9 @@ "imageUploadError": "Please upload a JPG, PNG, GIF, or WebP image file.", "blurStrength": "Blur Strength: {{strength}}", "solidColor": "Solid Color (Censorship)", - "borderRadius": "Border Radius" + "borderRadius": "Border Radius", + "customFonts": "Custom Fonts", + "color": "Color" }, "fontStyles": { diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json index a42ea123..60675da0 100644 --- a/src/i18n/locales/es/editor.json +++ b/src/i18n/locales/es/editor.json @@ -42,7 +42,9 @@ "imageUploadError": "Por favor sube un archivo de imagen JPG, PNG, GIF o WebP.", "blurStrength": "Fuerza del Desenfoque: {{strength}}", "solidColor": "Color Sólido (Censura)", - "borderRadius": "Radio del Borde" + "borderRadius": "Radio del Borde", + "customFonts": "Custom Fonts", + "color": "Color" }, "fontStyles": { diff --git a/src/i18n/locales/ko/editor.json b/src/i18n/locales/ko/editor.json index 56eb6bc5..d7753b30 100644 --- a/src/i18n/locales/ko/editor.json +++ b/src/i18n/locales/ko/editor.json @@ -43,7 +43,9 @@ "imageUploadError": "JPG, PNG, GIF 또는 WebP 이미지 파일을 업로드해 주세요.", "blurStrength": "블러 강도: {{strength}}", "solidColor": "단색 (검열)", - "borderRadius": "테두리 반경" + "borderRadius": "테두리 반경", + "customFonts": "Custom Fonts", + "color": "Color" }, "fontStyles": { diff --git a/src/i18n/locales/nl/editor.json b/src/i18n/locales/nl/editor.json index b174ca98..ac5c3593 100644 --- a/src/i18n/locales/nl/editor.json +++ b/src/i18n/locales/nl/editor.json @@ -43,7 +43,9 @@ "imageUploadError": "Upload een JPG-, PNG-, GIF- of WebP-afbeelding.", "blurStrength": "Vervagingssterkte: {{strength}}", "solidColor": "Effen Kleur (Censuur)", - "borderRadius": "Hoekradius" + "borderRadius": "Hoekradius", + "customFonts": "Custom Fonts", + "color": "Color" }, "fontStyles": { diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index 4a0c4a5d..dcc98018 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -42,7 +42,9 @@ "imageUploadError": "请上传 JPG、PNG、GIF 或 WebP 图片文件。", "blurStrength": "模糊强度: {{strength}}", "solidColor": "纯色 (审查)", - "borderRadius": "边框半径" + "borderRadius": "边框半径", + "customFonts": "Custom Fonts", + "color": "Color" }, "fontStyles": {