Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ interface Window {
error?: string;
};
}>;
openNotes: () => Promise<{
opened: boolean;
reason?: string;
}>;
selectSource: (source: ProcessedDesktopSource) => Promise<ProcessedDesktopSource | null>;
getSelectedSource: () => Promise<ProcessedDesktopSource | null>;
onSelectedSourceChanged: (callback: (source: ProcessedDesktopSource) => void) => () => void;
Expand Down
13 changes: 13 additions & 0 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1280,8 +1280,10 @@ export function registerIpcHandlers(
createEditorWindow: () => void,
createSourceSelectorWindow: () => BrowserWindow,
createCountdownOverlayWindow: () => BrowserWindow,
createNotesWindowWrapper: () => BrowserWindow,
getMainWindow: () => BrowserWindow | null,
getSourceSelectorWindow: () => BrowserWindow | null,
getNotesWindow: () => BrowserWindow | null,
getCountdownOverlayWindow?: () => BrowserWindow | null,
onRecordingStateChange?: (recording: boolean, sourceName: string) => void,
_switchToHud?: () => void,
Expand Down Expand Up @@ -1479,6 +1481,17 @@ export function registerIpcHandlers(
return { opened: true };
});

ipcMain.handle("open-notes", async () => {
const notesSelectorWin = getNotesWindow();
if (notesSelectorWin) {
notesSelectorWin.focus();
return { opened: true };
}

createNotesWindowWrapper();
return { opened: true };
});

ipcMain.handle("switch-to-editor", () => {
// createEditorWindow already closes the current mainWindow (the HUD) before
// opening the editor. Closing it here too double-closes, leaving ghost
Expand Down
17 changes: 17 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
createCountdownOverlayWindow,
createEditorWindow,
createHudOverlayWindow,
createNotesWindow,
createSourceSelectorWindow,
} from "./windows";

Expand Down Expand Up @@ -84,6 +85,7 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
let mainWindow: BrowserWindow | null = null;
let sourceSelectorWindow: BrowserWindow | null = null;
let countdownOverlayWindow: BrowserWindow | null = null;
let notesWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let selectedSourceName = "";
const isMac = process.platform === "darwin";
Expand Down Expand Up @@ -437,6 +439,19 @@ function createSourceSelectorWindowWrapper() {
return sourceSelectorWindow;
}

function createNotesWindowWrapper() {
{
notesWindow = createNotesWindow();
notesWindow.on("closed", () => {
notesWindow = null;
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("notes-window-closed");
}
});
return notesWindow;
}
}

function createCountdownOverlayWindowWrapper() {
if (countdownOverlayWindow && !countdownOverlayWindow.isDestroyed()) {
return countdownOverlayWindow;
Expand Down Expand Up @@ -575,8 +590,10 @@ appReady?.then(async () => {
createEditorWindowWrapper,
createSourceSelectorWindowWrapper,
createCountdownOverlayWindowWrapper,
createNotesWindowWrapper,
() => mainWindow,
() => sourceSelectorWindow,
() => notesWindow,
() => countdownOverlayWindow,
(recording: boolean, sourceName: string) => {
selectedSourceName = sourceName;
Expand Down
3 changes: 3 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
openSourceSelector: () => {
return ipcRenderer.invoke("open-source-selector");
},
openNotes: () => {
return ipcRenderer.invoke("open-notes");
},
selectSource: (source: ProcessedDesktopSource) => {
return ipcRenderer.invoke("select-source", source);
},
Expand Down
41 changes: 41 additions & 0 deletions electron/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,44 @@ export function createCountdownOverlayWindow(): BrowserWindow {

return win;
}

// Frameless Notes Window for taking notes during a recording.
export function createNotesWindow(): BrowserWindow {
const win = new BrowserWindow({
width: 400,
height: 540,
minWidth: 320,
minHeight: 400,
maxWidth: 640,
maxHeight: 720,
title: "OpenScreen - Notes",
backgroundColor: "#09090b",
resizable: true,
alwaysOnTop: true,
skipTaskbar: false,
show: false,
webPreferences: {
preload: path.join(__dirname, "preload.mjs"),
additionalArguments: [ASSET_BASE_URL_ARG],
nodeIntegration: false,
contextIsolation: true,
backgroundThrottling: false,
},
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

win.setContentProtection(true);
win.once("ready-to-show", () => {
win.setContentProtection(true);
win.show();
});

if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL + "?showNotes=true");
} else {
win.loadFile(path.join(RENDERER_DIST, "index.html"), {
query: { showNotes: "true" },
});
}

return win;
}
10 changes: 8 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { lazy, Suspense, useEffect, useState } from "react";
import { CountdownOverlay } from "./components/launch/CountdownOverlay.tsx";
import { LaunchWindow } from "./components/launch/LaunchWindow";
import { NotesWindow } from "./components/launch/NotesWindow.tsx";
import { SourceSelector } from "./components/launch/SourceSelector";
import { Toaster } from "./components/ui/sonner";
import { TooltipProvider } from "./components/ui/tooltip";
Expand All @@ -19,6 +20,8 @@ export default function App() {
const [windowType, setWindowType] = useState(
() => new URLSearchParams(window.location.search).get("windowType") || "",
);
const showNotes = new URLSearchParams(window.location.search).get("showNotes") === "true";

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const tEditor = useScopedT("editor");

useEffect(() => {
Expand Down Expand Up @@ -102,15 +105,18 @@ export default function App() {
);
default:
return (
<div className="w-full h-full bg-background text-foreground">
<h1>Openscreen</h1>
<div>
<div className="w-full h-full bg-background text-foreground">
<h1>Openscreen</h1>
</div>
</div>
);
}
})();

return (
<TooltipProvider>
{showNotes && <NotesWindow />}
{content}
Comment on lines 117 to 120

@coderabbitai coderabbitai Bot Jun 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the notes render path exclusive.

Per the new route contract, the notes window is opened with ?showNotes=true. In that case windowType stays empty, so content still falls through to the default "Openscreen" panel and renders alongside NotesWindow at Line 120. That breaks the notes-only window.

Suggested fix
	return (
		<TooltipProvider>
-			{showNotes && <NotesWindow />}
-			{content}
+			{showNotes ? <NotesWindow /> : content}
			<Toaster theme="dark" />
		</TooltipProvider>
	);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<TooltipProvider>
{showNotes && <NotesWindow />}
{content}
return (
<TooltipProvider>
{showNotes ? <NotesWindow /> : content}
<Toaster theme="dark" />
</TooltipProvider>
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/App.tsx` around lines 117 - 120, The notes render path in App.tsx is not
exclusive, so when showNotes is true the NotesWindow renders but content still
falls through to the default Openscreen panel. Update the render logic around
TooltipProvider, NotesWindow, and the content branch so the notes route
short-circuits the default panel and only one view is shown. Use the existing
showNotes and windowType checks to make the NotesWindow path mutually exclusive
from the fallback content.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work cause we will need noteswindow to show up when content is also open

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Itzadetunji I think we’re talking about two different scopes here.

My concern was only about what renders inside the window opened with ?showNotes=true. Since Notes is implemented as a separate Electron BrowserWindow, making this branch exclusive would not stop the main/HUD content from being open at the same time in its own window.

In other words:

  • the HUD/main window still renders its normal content
  • the notes window should render only NotesWindow

Right now, in the notes window, windowType is empty, so content falls back to the default "Openscreen" panel and gets rendered alongside NotesWindow at Line 120.

So the exclusivity is per-renderer-window, not app-wide. If you do intend to support a case where the same window can have both showNotes=true and a real windowType, let me know — but based on the PR description, this still looks like a valid issue.

(ᵔᴥᵔ)

<Toaster theme="dark" />
</TooltipProvider>
Expand Down
31 changes: 28 additions & 3 deletions src/components/launch/LaunchWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Check, ChevronDown, Clapperboard, Columns3, Languages, Rows3 } from "lucide-react";
import {
Check,
ChevronDown,
Clapperboard,
Columns3,
Languages,
NotepadText,
Rows3,
} from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs";
Expand Down Expand Up @@ -697,7 +705,10 @@ export function LaunchWindow() {
onMouseLeave={() => setIsMicHovered(false)}
onFocus={() => setIsMicFocused(true)}
onBlur={() => setIsMicFocused(false)}
style={{ width: micExpanded ? "240px" : "140px", transition: "width 300ms ease" }}
style={{
width: micExpanded ? "240px" : "140px",
transition: "width 300ms ease",
}}
>
<div className="relative flex-1 min-w-0">
{!micExpanded && (
Expand Down Expand Up @@ -743,7 +754,10 @@ export function LaunchWindow() {
onMouseLeave={() => setIsWebcamHovered(false)}
onFocus={() => setIsWebcamFocused(true)}
onBlur={() => setIsWebcamFocused(false)}
style={{ width: webcamExpanded ? "240px" : "140px", transition: "width 300ms ease" }}
style={{
width: webcamExpanded ? "240px" : "140px",
transition: "width 300ms ease",
}}
>
<div className="relative flex-1 min-w-0">
{!webcamExpanded && (
Expand Down Expand Up @@ -1031,6 +1045,17 @@ export function LaunchWindow() {
</div>
)}

<Tooltip content={t("tooltips.openNotes")}>
<button
type="button"
aria-label={t("tooltips.openNotes")}
className={`${hudIconBtnClasses} ${styles.electronNoDrag}`}
onClick={() => window.electronAPI.openNotes()}
>
<NotepadText size={ICON_SIZE} className="text-white/60" />
</button>
</Tooltip>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

{!recording && (
<Tooltip content={t("tooltips.openStudio")}>
<button
Expand Down
27 changes: 27 additions & 0 deletions src/components/launch/NotesWindow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useLayoutEffect, useState } from "react";
import { useScopedT } from "@/contexts/I18nContext";

export function NotesWindow() {
const t = useScopedT("launch");
const [notes, setNotes] = useState("");

const handleNotesChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
localStorage.setItem("notes", e.target.value);
setNotes(e.target.value);
};

useLayoutEffect(() => {
setNotes(localStorage.getItem("notes") ?? "");
}, []);

return (
<div className="bg-white h-screen w-screen px-6 py-4">
<textarea
className="w-full h-full bg-transparent outline-none resize-none caret-black text-black"
placeholder={t("tooltips.openNotesPlaceholder")}
value={notes}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
onChange={handleNotesChange}
/>
</div>
);
}
4 changes: 3 additions & 1 deletion src/i18n/locales/en/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"openProject": "Open project",
"useVerticalTray": "Use vertical tray",
"useHorizontalTray": "Use horizontal tray",
"openStudio": "Open Studio"
"openStudio": "Open Studio",
"openNotes": "Open Notes",
"openNotesPlaceholder": "Take notes here..."
},
"audio": {
"enableSystemAudio": "Enable system audio",
Expand Down
2 changes: 2 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { I18nProvider } from "./contexts/I18nContext";
import "./index.css";

const windowType = new URLSearchParams(window.location.search).get("windowType") || "";
const showNotes = new URLSearchParams(window.location.search).get("showNotes") === "true";
if (
showNotes ||
windowType === "hud-overlay" ||
windowType === "source-selector" ||
windowType === "countdown-overlay"
Expand Down
Loading