From eb228264ac71029ab40382b902d524aa851ffa04 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Sun, 12 Jul 2026 02:39:58 +0530 Subject: [PATCH 1/5] perf: lazy load lottie assets --- src/components/DownloadResult.tsx | 24 +++++++++++++++++++-- src/components/ExportOverlay.tsx | 36 +++++++++++++++++++++++++------ src/components/FileUpload.tsx | 28 ++++++++++++++++++++---- 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/components/DownloadResult.tsx b/src/components/DownloadResult.tsx index 9cb04286..e510163c 100644 --- a/src/components/DownloadResult.tsx +++ b/src/components/DownloadResult.tsx @@ -6,7 +6,6 @@ import { formatBytes } from "@/lib/utils"; import { Download, RotateCcw, Share2, AlertCircle, Volume2, VolumeX } from "lucide-react"; import LottiePlayer from "./LottiePlayer"; import { NativeShareButton } from "./NativeShareButton"; -import successAnim from "@/lib/lottie/success.json"; import { cn } from "@/lib/utils"; const SHARE_TWEET_TEXT = @@ -32,6 +31,7 @@ interface Props { export default function DownloadResult({ result, onReset, soundOnCompletion, onToggleSound }: Props) { const defaultName = `reframe_${result.width}x${result.height}`; const [name, setName] = useState(defaultName); + const [successAnim, setSuccessAnim] = useState(null); const invalidCharRegex = /[<>:"/\\|?*]/; const isValid = !invalidCharRegex.test(name) && name.trim().length > 0; @@ -50,6 +50,26 @@ export default function DownloadResult({ result, onReset, soundOnCompletion, onT }); } }, [soundOnCompletion]); + + useEffect(() => { + let cancelled = false; + + import("@/lib/lottie/success.json") + .then((mod) => { + if (!cancelled) { + setSuccessAnim(mod.default ?? mod); + } + }) + .catch(() => { + if (!cancelled) { + setSuccessAnim(null); + } + }); + + return () => { + cancelled = true; + }; + }, []); const handleReset = () => { if (window.confirm("This will clear the current video and all settings. Continue?")) { onReset(); @@ -61,7 +81,7 @@ export default function DownloadResult({ result, onReset, soundOnCompletion, onT
- + {successAnim ? : null}

Export complete

diff --git a/src/components/ExportOverlay.tsx b/src/components/ExportOverlay.tsx index 702461cb..5cae1aa6 100644 --- a/src/components/ExportOverlay.tsx +++ b/src/components/ExportOverlay.tsx @@ -4,7 +4,6 @@ import FocusTrap from "focus-trap-react"; import { useEffect, useRef, useCallback, useState } from "react"; import { ExportStatus } from "@/lib/types"; import LottiePlayer from "./LottiePlayer"; -import spinnerAnim from "@/lib/lottie/spinner.json"; import TipCarousel from "./TipCarousel"; interface Props { @@ -26,6 +25,7 @@ export default function ExportOverlay({ status, progress, exportStartedAt, onCan const previousFocusRef = useRef(null); const focusAnchorRef = useRef(null); const [elapsedMs, setElapsedMs] = useState(0); + const [spinnerAnim, setSpinnerAnim] = useState(null); const handleKeyDown = useCallback((e: KeyboardEvent) => { if (e.key === "Escape") { @@ -74,6 +74,26 @@ export default function ExportOverlay({ status, progress, exportStartedAt, onCan return () => window.clearInterval(timer); }, [status, exportStartedAt]); + useEffect(() => { + let cancelled = false; + + import("@/lib/lottie/spinner.json") + .then((mod) => { + if (!cancelled) { + setSpinnerAnim(mod.default ?? mod); + } + }) + .catch(() => { + if (!cancelled) { + setSpinnerAnim(null); + } + }); + + return () => { + cancelled = true; + }; + }, []); + if (!visible) return null; const isLoading = status === "loading-engine"; @@ -105,12 +125,14 @@ export default function ExportOverlay({ status, progress, exportStartedAt, onCan aria-hidden="true" />
-

diff --git a/src/components/FileUpload.tsx b/src/components/FileUpload.tsx index f94c17ef..99824b4c 100644 --- a/src/components/FileUpload.tsx +++ b/src/components/FileUpload.tsx @@ -3,7 +3,6 @@ import { useRef, useState, useEffect, useCallback } from "react"; import { Film, FolderOpen } from "lucide-react"; import LottiePlayer from "./LottiePlayer"; -import uploadAnim from "@/lib/lottie/upload.json"; import { cn, formatBytes, formatDuration } from "@/lib/utils"; import { MAX_FILE_SIZE, WARNING_FILE_SIZE } from "@/lib/types"; @@ -26,6 +25,7 @@ export default function FileUpload({ const [pageDragging, setPageDragging] = useState(false); const [error, setError] = useState(""); const [warning, setWarning] = useState(""); + const [uploadAnim, setUploadAnim] = useState(null); const dragCounterRef = useRef(0); // ── Keyboard shortcut Ctrl+O ────────────────────────── @@ -40,6 +40,26 @@ export default function FileUpload({ return () => document.removeEventListener("keydown", handler); }, []); + useEffect(() => { + let cancelled = false; + + import("@/lib/lottie/upload.json") + .then((mod) => { + if (!cancelled) { + setUploadAnim(mod.default ?? mod); + } + }) + .catch(() => { + if (!cancelled) { + setUploadAnim(null); + } + }); + + return () => { + cancelled = true; + }; + }, []); + // ── Page-level drag overlay ─────────────────────────── // Uses a counter so nested dragenter/dragleave don't flicker useEffect(() => { @@ -174,7 +194,7 @@ export default function FileUpload({ { const f = e.target.files?.[0]; @@ -216,7 +236,7 @@ export default function FileUpload({ )}
- + {uploadAnim ? : null}
@@ -305,7 +325,7 @@ export default function FileUpload({ { const f = e.target.files?.[0]; From a7c1e1431fc16b1709c9068ab98df6080d695d32 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Tue, 21 Jul 2026 15:32:36 +0530 Subject: [PATCH 2/5] fix: variable shadowing prevents log event listener cleanup --- src/lib/ffmpeg.worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ffmpeg.worker.ts b/src/lib/ffmpeg.worker.ts index f72ef583..4cd4cf96 100644 --- a/src/lib/ffmpeg.worker.ts +++ b/src/lib/ffmpeg.worker.ts @@ -486,7 +486,7 @@ async function runExport(request: ExportRequest): Promise { } let missingAudioDetected = false; - const logListener = ({ message }: { message: string }) => { + logListener = ({ message }: { message: string }) => { const msg = message.toLowerCase(); if ( msg.includes("matches no streams") || From dbd377f788f6207c25448ddf84077256387a303d Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Tue, 21 Jul 2026 15:33:57 +0530 Subject: [PATCH 3/5] fix: duplicate fontfile parameters in drawtext filter --- src/lib/text-overlay.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib/text-overlay.ts b/src/lib/text-overlay.ts index b7ca5c5c..66c0d603 100644 --- a/src/lib/text-overlay.ts +++ b/src/lib/text-overlay.ts @@ -91,15 +91,14 @@ export function buildTextFilter( // Build the drawtext filter with font support let filter = `drawtext=text='${escapedText}':x=${pixelX}:y=${pixelY}:fontsize=${overlay.fontSize}:fontcolor=${overlay.color}:fontweight=${fontWeightParam}`; - // Add font family if specified - if (overlay.fontFamily) { - // Sanitize font name for FFmpeg + // Use font file path if available, otherwise fall back to font name + if (fontFileParam) { + filter += `:${fontFileParam}`; + } else if (overlay.fontFamily) { + // Sanitize font name for FFmpeg (fallback when no font path available) const safeFontName = overlay.fontFamily.replace(/[^a-zA-Z0-9-]/g, ""); filter += `:fontfile='${safeFontName}'`; } - - // Add custom font file path if available - if (fontFileParam) { filter += `:${fontFileParam}`; } From 25cbcc7a6a045a8f66372dba9255bf305e276e1c Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Tue, 21 Jul 2026 15:35:03 +0530 Subject: [PATCH 4/5] fix: missing denoise field validation in isValidRecipe --- src/lib/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/types.ts b/src/lib/types.ts index 90827df9..6e47afe1 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -140,6 +140,7 @@ export function isValidRecipe(value: unknown): value is EditRecipe { if (typeof v.brightness !== "number" || !isFinite(v.brightness)) return false; if (typeof v.contrast !== "number" || !isFinite(v.contrast)) return false; if (typeof v.saturation !== "number" || !isFinite(v.saturation)) return false; + if (typeof v.denoise !== "boolean") return false; if (typeof v.soundOnCompletion !== "boolean") return false; if (!Array.isArray(v.textOverlays)) return false; From 42601380afa2127a5a1c0690c51270e0a9f12e23 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Tue, 21 Jul 2026 15:36:42 +0530 Subject: [PATCH 5/5] fix: localStorage access without try/catch crashes in private browsing --- src/components/OnboardingTour.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/OnboardingTour.tsx b/src/components/OnboardingTour.tsx index c1cef066..a89ca11a 100644 --- a/src/components/OnboardingTour.tsx +++ b/src/components/OnboardingTour.tsx @@ -269,7 +269,11 @@ export default function OnboardingTour() { // Initialise on mount useEffect(() => { - if (localStorage.getItem(TOUR_KEY)) return; + try { + if (localStorage.getItem(TOUR_KEY)) return; + } catch { + return; + } const t = setTimeout(async () => { const rect = await measureTarget(TOUR_STEPS[0]?.targetId ?? ""); if (rect) {