From d5f59a7b8e1fa49590a26babd04518a39f406ce0 Mon Sep 17 00:00:00 2001
From: JasonOA888
Date: Sat, 4 Apr 2026 23:16:39 +0800
Subject: [PATCH 1/6] fix: persist user settings across sessions
Add userPreferences module to save/load padding, aspect ratio,
export format and quality to localStorage. Applied on mount
in VideoEditor.
Closes #306
---
src/components/video-editor/VideoEditor.tsx | 1 +
src/lib/userPreferences.ts | 69 +++++++++++++++++++++
2 files changed, 70 insertions(+)
create mode 100644 src/lib/userPreferences.ts
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 4e5e978..e2e34f1 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -21,6 +21,7 @@ import {
VideoExporter,
} from "@/lib/exporter";
import type { ProjectMedia } from "@/lib/recordingSession";
+import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences";
import { matchesShortcut } from "@/lib/shortcuts";
import {
getAspectRatioValue,
diff --git a/src/lib/userPreferences.ts b/src/lib/userPreferences.ts
new file mode 100644
index 0000000..ae9d14f
--- /dev/null
+++ b/src/lib/userPreferences.ts
@@ -0,0 +1,69 @@
+import type { ExportFormat, ExportQuality } from "@/lib/exporter";
+import type { AspectRatio } from "@/utils/aspectRatioUtils";
+
+const PREFS_KEY = "openscreen_user_preferences";
+
+export interface UserPreferences {
+ /** Default padding % */
+ padding: number;
+ /** Default aspect ratio */
+ aspectRatio: AspectRatio;
+ /** Default export quality */
+ exportQuality: ExportQuality;
+ /** Default export format */
+ exportFormat: ExportFormat;
+}
+
+const DEFAULT_PREFS: UserPreferences = {
+ padding: 50,
+ aspectRatio: "16:9",
+ exportQuality: "good",
+ exportFormat: "mp4",
+};
+
+function safeJsonParse(text: string | null): Record | null {
+ if (!text) return null;
+ try {
+ return JSON.parse(text);
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Load persisted user preferences from localStorage.
+ * Returns defaults for any missing or invalid fields.
+ */
+export function loadUserPreferences(): UserPreferences {
+ const raw = safeJsonParse(localStorage.getItem(PREFS_KEY));
+ if (!raw || typeof raw !== "object") return { ...DEFAULT_PREFS };
+
+ return {
+ padding:
+ typeof raw.padding === "number" && Number.isFinite(raw.padding) && raw.padding >= 0 && raw.padding <= 100
+ ? raw.padding
+ : DEFAULT_PREFS.padding,
+ aspectRatio:
+ typeof raw.aspectRatio === "string" ? (raw.aspectRatio as AspectRatio) : DEFAULT_PREFS.aspectRatio,
+ exportQuality:
+ raw.exportQuality === "medium" || raw.exportQuality === "source"
+ ? (raw.exportQuality as ExportQuality)
+ : DEFAULT_PREFS.exportQuality,
+ exportFormat:
+ raw.exportFormat === "gif" ? (raw.exportFormat as ExportFormat) : DEFAULT_PREFS.exportFormat,
+ };
+}
+
+/**
+ * Persist user preferences to localStorage.
+ * Only the explicitly provided fields are updated.
+ */
+export function saveUserPreferences(partial: Partial): void {
+ const current = loadUserPreferences();
+ const merged = { ...current, ...partial };
+ try {
+ localStorage.setItem(PREFS_KEY, JSON.stringify(merged));
+ } catch {
+ // localStorage may be unavailable (e.g. private browsing quota exceeded)
+ }
+}
From 7d746196d2c26e4e6f742177fa3e6861940b50b3 Mon Sep 17 00:00:00 2001
From: JasonOA888
Date: Sat, 4 Apr 2026 23:27:56 +0800
Subject: [PATCH 2/6] fix: persist user settings across sessions (closes #306)
Load saved preferences (padding, aspect ratio, export quality, export format)
on mount and auto-save whenever these settings change. Uses the existing
userPreferences.ts utility with a ref guard to prevent overwriting saved prefs
with defaults before the initial load completes.
---
src/components/video-editor/VideoEditor.tsx | 26 ++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index e2e34f1..6d7c5c5 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -21,8 +21,8 @@ import {
VideoExporter,
} from "@/lib/exporter";
import type { ProjectMedia } from "@/lib/recordingSession";
-import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences";
import { matchesShortcut } from "@/lib/shortcuts";
+import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences";
import {
getAspectRatioValue,
getNativeAspectRatioValue,
@@ -360,6 +360,30 @@ export default function VideoEditor() {
loadInitialData();
}, [applyLoadedProject]);
+ // Track whether user preferences have been loaded to avoid
+ // overwriting saved prefs with defaults on the first render
+ const prefsLoadedRef = useRef(false);
+
+ // Load persisted user preferences on mount
+ useEffect(() => {
+ const prefs = loadUserPreferences();
+ updateState({
+ padding: prefs.padding,
+ aspectRatio: prefs.aspectRatio,
+ });
+ setExportQuality(prefs.exportQuality);
+ setExportFormat(prefs.exportFormat);
+ prefsLoadedRef.current = true;
+ // We intentionally only want this to run once on mount
+ // biome-ignore lint/correctness/useExhaustiveDependencies: mount-only effect
+ }, []);
+
+ // Auto-save user preferences when settings change
+ useEffect(() => {
+ if (!prefsLoadedRef.current) return;
+ saveUserPreferences({ padding, aspectRatio, exportQuality, exportFormat });
+ }, [padding, aspectRatio, exportQuality, exportFormat]);
+
const saveProject = useCallback(
async (forceSaveAs: boolean) => {
if (!videoPath) {
From 4f48ecd4bc796a43fc16c34f0f7acad400345ac2 Mon Sep 17 00:00:00 2001
From: JasonOA888
Date: Sat, 4 Apr 2026 23:58:25 +0800
Subject: [PATCH 3/6] fix: address code review feedback for settings
persistence
- Replace useRef with useState for prefsHydrated to prevent race condition
- Wrap localStorage.getItem in try/catch in loadUserPreferences
- Validate aspectRatio against known valid values
- Include 'good' in exportQuality validation, 'mp4' in exportFormat validation
---
package-lock.json | 4 +--
src/components/video-editor/VideoEditor.tsx | 8 +++---
src/lib/userPreferences.ts | 28 ++++++++++++++++++---
3 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 70e3395..fdbd6b9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "openscreen",
- "version": "1.2.0",
+ "version": "1.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openscreen",
- "version": "1.2.0",
+ "version": "1.3.0",
"dependencies": {
"@fix-webm-duration/fix": "^1.0.1",
"@pixi/filter-drop-shadow": "^5.2.0",
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 6d7c5c5..4168ef8 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -362,7 +362,7 @@ export default function VideoEditor() {
// Track whether user preferences have been loaded to avoid
// overwriting saved prefs with defaults on the first render
- const prefsLoadedRef = useRef(false);
+ const [prefsHydrated, setPrefsHydrated] = useState(false);
// Load persisted user preferences on mount
useEffect(() => {
@@ -373,16 +373,16 @@ export default function VideoEditor() {
});
setExportQuality(prefs.exportQuality);
setExportFormat(prefs.exportFormat);
- prefsLoadedRef.current = true;
+ setPrefsHydrated(true);
// We intentionally only want this to run once on mount
// biome-ignore lint/correctness/useExhaustiveDependencies: mount-only effect
}, []);
// Auto-save user preferences when settings change
useEffect(() => {
- if (!prefsLoadedRef.current) return;
+ if (!prefsHydrated) return;
saveUserPreferences({ padding, aspectRatio, exportQuality, exportFormat });
- }, [padding, aspectRatio, exportQuality, exportFormat]);
+ }, [prefsHydrated, padding, aspectRatio, exportQuality, exportFormat]);
const saveProject = useCallback(
async (forceSaveAs: boolean) => {
diff --git a/src/lib/userPreferences.ts b/src/lib/userPreferences.ts
index ae9d14f..5839799 100644
--- a/src/lib/userPreferences.ts
+++ b/src/lib/userPreferences.ts
@@ -3,6 +3,17 @@ import type { AspectRatio } from "@/utils/aspectRatioUtils";
const PREFS_KEY = "openscreen_user_preferences";
+const VALID_ASPECT_RATIOS: readonly string[] = [
+ "16:9",
+ "9:16",
+ "1:1",
+ "4:3",
+ "4:5",
+ "16:10",
+ "10:16",
+ "native",
+];
+
export interface UserPreferences {
/** Default padding % */
padding: number;
@@ -35,7 +46,12 @@ function safeJsonParse(text: string | null): Record | null {
* Returns defaults for any missing or invalid fields.
*/
export function loadUserPreferences(): UserPreferences {
- const raw = safeJsonParse(localStorage.getItem(PREFS_KEY));
+ let raw: Record | null = null;
+ try {
+ raw = safeJsonParse(localStorage.getItem(PREFS_KEY));
+ } catch {
+ return { ...DEFAULT_PREFS };
+ }
if (!raw || typeof raw !== "object") return { ...DEFAULT_PREFS };
return {
@@ -44,13 +60,17 @@ export function loadUserPreferences(): UserPreferences {
? raw.padding
: DEFAULT_PREFS.padding,
aspectRatio:
- typeof raw.aspectRatio === "string" ? (raw.aspectRatio as AspectRatio) : DEFAULT_PREFS.aspectRatio,
+ typeof raw.aspectRatio === "string" && VALID_ASPECT_RATIOS.includes(raw.aspectRatio)
+ ? (raw.aspectRatio as AspectRatio)
+ : DEFAULT_PREFS.aspectRatio,
exportQuality:
- raw.exportQuality === "medium" || raw.exportQuality === "source"
+ raw.exportQuality === "medium" || raw.exportQuality === "good" || raw.exportQuality === "source"
? (raw.exportQuality as ExportQuality)
: DEFAULT_PREFS.exportQuality,
exportFormat:
- raw.exportFormat === "gif" ? (raw.exportFormat as ExportFormat) : DEFAULT_PREFS.exportFormat,
+ raw.exportFormat === "gif" || raw.exportFormat === "mp4"
+ ? (raw.exportFormat as ExportFormat)
+ : DEFAULT_PREFS.exportFormat,
};
}
From a8427b950e29641a38b136cd2afae7f96e5dc388 Mon Sep 17 00:00:00 2001
From: JasonOA888
Date: Mon, 6 Apr 2026 02:01:01 +0800
Subject: [PATCH 4/6] fix: resolve lint errors for CI
- Add updateState to useEffect dependency array
- Remove ineffective biome-ignore suppression comment
- Fix formatting in userPreferences.ts per biome rules
---
src/components/video-editor/VideoEditor.tsx | 6 ++----
src/lib/userPreferences.ts | 9 +++++++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 4168ef8..a85ccaf 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -364,7 +364,7 @@ export default function VideoEditor() {
// overwriting saved prefs with defaults on the first render
const [prefsHydrated, setPrefsHydrated] = useState(false);
- // Load persisted user preferences on mount
+ // Load persisted user preferences on mount (intentionally runs once)
useEffect(() => {
const prefs = loadUserPreferences();
updateState({
@@ -374,9 +374,7 @@ export default function VideoEditor() {
setExportQuality(prefs.exportQuality);
setExportFormat(prefs.exportFormat);
setPrefsHydrated(true);
- // We intentionally only want this to run once on mount
- // biome-ignore lint/correctness/useExhaustiveDependencies: mount-only effect
- }, []);
+ }, [updateState]);
// Auto-save user preferences when settings change
useEffect(() => {
diff --git a/src/lib/userPreferences.ts b/src/lib/userPreferences.ts
index 5839799..e060788 100644
--- a/src/lib/userPreferences.ts
+++ b/src/lib/userPreferences.ts
@@ -56,7 +56,10 @@ export function loadUserPreferences(): UserPreferences {
return {
padding:
- typeof raw.padding === "number" && Number.isFinite(raw.padding) && raw.padding >= 0 && raw.padding <= 100
+ typeof raw.padding === "number" &&
+ Number.isFinite(raw.padding) &&
+ raw.padding >= 0 &&
+ raw.padding <= 100
? raw.padding
: DEFAULT_PREFS.padding,
aspectRatio:
@@ -64,7 +67,9 @@ export function loadUserPreferences(): UserPreferences {
? (raw.aspectRatio as AspectRatio)
: DEFAULT_PREFS.aspectRatio,
exportQuality:
- raw.exportQuality === "medium" || raw.exportQuality === "good" || raw.exportQuality === "source"
+ raw.exportQuality === "medium" ||
+ raw.exportQuality === "good" ||
+ raw.exportQuality === "source"
? (raw.exportQuality as ExportQuality)
: DEFAULT_PREFS.exportQuality,
exportFormat:
From b6803eb6e331044ea1bf1c675922ab11e02fd4b2 Mon Sep 17 00:00:00 2001
From: Anirudh Vempati <40335580+notrudyyy@users.noreply.github.com>
Date: Mon, 6 Apr 2026 04:28:15 +0530
Subject: [PATCH 5/6] Update README.md
---
README.md | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 53d5479..17169c0 100644
--- a/README.md
+++ b/README.md
@@ -30,16 +30,15 @@ OpenScreen is 100% free for personal and commercial use. Use it, modify it, dist
## Core Features
-- Record your whole screen or specific windows.
-- Add Automatic zooms or manual zooms (customizable depth levels).
-- Record microphone audio and system audio capture.
-- Customize the duration and position of zooms however you please.
+- Record specific windows or your whole screen.
+- Add automatic or manual zooms (adjustable depth levels) and customize their durarion and position.
+- Record microphone and system audio.
- Crop video recordings to hide parts.
- Choose between wallpapers, solid colors, gradients or a custom background.
- Motion blur for smoother pan and zoom effects.
- Add annotations (text, arrows, images).
- Trim sections of the clip.
-- Customize speed at different segments.
+- Customize the speed of different segments.
- Export in different aspect ratios and resolutions.
## Installation
@@ -78,9 +77,9 @@ You may need to grant screen recording permissions depending on your desktop env
System audio capture relies on Electron's [desktopCapturer](https://www.electronjs.org/docs/latest/api/desktop-capturer) and has some platform-specific quirks:
-- **macOS**: Requires macOS 13+. On macOS 14.2+ you'll be prompted to grant audio capture permission. macOS 12 and below does not support system audio (mic still work).
+- **macOS**: Requires macOS 13+. On macOS 14.2+ you'll be prompted to grant audio capture permission. macOS 12 and below does not support system audio (mic still works).
- **Windows**: Works out of the box.
-- **Linux**: Needs PipeWire (default on Ubuntu 22.04+, Fedora 34+). Older PulseAudio-only setups may not support system audio (mic should still works).
+- **Linux**: Needs PipeWire (default on Ubuntu 22.04+, Fedora 34+). Older PulseAudio-only setups may not support system audio (mic should still work).
## Built with
- Electron
From da79dab756dcf7058ed7bf8ff4633cb94a0cbfab Mon Sep 17 00:00:00 2001
From: Anirudh Vempati <40335580+notrudyyy@users.noreply.github.com>
Date: Mon, 6 Apr 2026 04:37:49 +0530
Subject: [PATCH 6/6] Update preview image sizes to be dynamic
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 17169c0..b42355e 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,8 @@ Screen Studio is an awesome product and this is definitely not a 1:1 clone. Open
OpenScreen is 100% free for personal and commercial use. Use it, modify it, distribute it. (Just be cool 😁 and give a shoutout if you feel like it !)
-
-
+
+
## Core Features