Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feat: replace native OS close dialog with custom in-app dialog#521

Open
makaradam wants to merge 5 commits intosiddharthvaddem:mainsiddharthvaddem/openscreen:mainfrom
makaradam:feature/save-dialog-redesignmakaradam/openscreen:feature/save-dialog-redesignCopy head branch name to clipboard
Open

feat: replace native OS close dialog with custom in-app dialog#521
makaradam wants to merge 5 commits intosiddharthvaddem:mainsiddharthvaddem/openscreen:mainfrom
makaradam:feature/save-dialog-redesignmakaradam/openscreen:feature/save-dialog-redesignCopy head branch name to clipboard

Conversation

@makaradam
Copy link
Copy Markdown

@makaradam makaradam commented May 2, 2026

Closes #520

SaveDialog

Replaces dialog.showMessageBoxSync in the main process with a custom React dialog rendered inside the Electron window.

What changed

electron/main.ts — close event now sends request-close-confirm IPC to the renderer instead of calling the native dialog
electron/preload.ts + electron/electron-env.d.ts — two new bridge functions: onRequestCloseConfirm and sendCloseConfirmResponse
UnsavedChangesDialog.tsx — new component matching the app's dark theme, with the app logo and the same three actions
VideoEditor.tsx — registers the IPC listener and wires up the dialog state

What didn't change

The three-action behavior is identical — Save & Close still triggers the existing request-save-before-close / save-before-close-done flow, Discard still force-closes, Cancel keeps the window open. No functional changes.

Tested on Windows — all three actions verified including the save flow.

Summary by CodeRabbit

  • New Features

    • In-app unsaved-changes confirmation dialog with Save, Discard, and Cancel actions when closing the editor.
    • Dialog uses localized text, imagery, and full-width action buttons for clarity.
  • Bug Fixes

    • Editor close flow updated to use the in-app confirmation sequence to improve save-before-close reliability and avoid blocking native prompts.

@makaradam makaradam requested a review from siddharthvaddem as a code owner May 2, 2026 10:47
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a16e1065-8e8c-4dc9-b8fa-8b07c3dca98e

📥 Commits

Reviewing files that changed from the base of the PR and between e7e4932 and e2bdfee.

📒 Files selected for processing (1)
  • electron/main.ts
✅ Files skipped from review due to trivial changes (1)
  • electron/main.ts

📝 Walkthrough

Walkthrough

Main process close-confirm flow moved from a synchronous native dialog to an async, renderer-driven IPC flow: main prevents close, sends request-close-confirm; renderer shows an in-app UnsavedChangesDialog and replies with close-confirm-response ("save" | "discard" | "cancel"); main then saves (awaiting save-before-close-done), discards, or aborts close.

Changes

Close Dialog Conversion

Layer / File(s) Summary
Type Declarations
electron/electron-env.d.ts
Adds Window.electronAPI.onRequestCloseConfirm(callback): () => void and sendCloseConfirmResponse(choice: "save" | "discard" | "cancel"): void.
Preload Bridge
electron/preload.ts
Exposes onRequestCloseConfirm (registers "request-close-confirm" listener and returns cleanup) and sendCloseConfirmResponse (sends "close-confirm-response" with choice).
Main Process Close Flow
electron/main.ts
Removes dialog.showMessageBoxSync; adds isCloseConfirmInFlight guard, prevents default close, sends "request-close-confirm", awaits a one-time "close-confirm-response", handles "save" (sends request-save-before-close and waits for save-before-close-done), "discard" (close via forceCloseEditorWindow), and "cancel" (abort and clear guard).
Dialog UI Component
src/components/video-editor/UnsavedChangesDialog.tsx
New React component UnsavedChangesDialog with props isOpen, onSaveAndClose, onDiscardAndClose, onCancel; renders localized title/message/detail and three full-width buttons wired to callbacks.
Integration & Wiring
src/components/video-editor/VideoEditor.tsx
Imports and renders UnsavedChangesDialog; adds showCloseConfirmDialog state, useEffect to register onRequestCloseConfirm, and handlers that call sendCloseConfirmResponse("save" | "discard" | "cancel") and hide the dialog.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • siddharthvaddem

Poem

midnight electrons whisper low,
the main asks renderer: “hold or go?”
a tiny dialog steals the show,
save, discard, or gently let go —
IPC nods, the window breathes slow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: replacing the native OS close dialog with a custom in-app dialog, matching the PR's core objective.
Description check ✅ Passed Description covers motivation (visual inconsistency), changes made (four files modified), testing (Windows verified), and includes before/after screenshot. Follows template reasonably well with Purpose, Motivation, and Testing sections present.
Linked Issues check ✅ Passed PR fully addresses #520: replaces dialog.showMessageBoxSync with custom React component [electron/main.ts], maintains identical three-action behavior, preserves save-before-close flow, tested across actions including save.
Out of Scope Changes check ✅ Passed All changes directly support the close-confirm dialog redesign: IPC bridge functions, new UI component, state management in VideoEditor, and type definitions. No unrelated refactoring or cleanup detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 11 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3469c469b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread electron/main.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@electron/main.ts`:
- Around line 293-310: The close-confirm handshake can be re-entered causing
multiple pending ipcMain.once handlers; add a per-window "close confirmation in
flight" guard (e.g., a WeakMap<BrowserWindow, boolean> or a property on the
window) checked at the top of the close flow and set to true just before sending
windowToClose.webContents.send("request-close-confirm"), then clear the flag in
every terminal branch of the ipc handlers (on "close-confirm-response" for
cancel/discard, and inside the "save-before-close-done" handler whether
shouldClose is true or false) so subsequent close events are ignored until the
confirmation completes; keep references to the same event names
("close-confirm-response", "save-before-close-done") and call
forceCloseEditorWindow(windowToClose) only once after clearing the flag
appropriately.

In `@src/components/video-editor/UnsavedChangesDialog.tsx`:
- Around line 22-76: UnsavedChangesDialog is rendering a hand-rolled modal that
lacks the shared Dialog semantics (focus trap, Escape handling, accessible
names)—replace the outer fragments and fixed divs with the app's Dialog
primitive used in VideoEditor.tsx, wiring Dialog's open/onOpenChange (or
onClose) to the existing onCancel handler so Escape and backdrop clicks call
onCancel, move the title/description into Dialog.Title/Description (or the
primitive's equivalents) using td("unsavedChanges.title") and
td("unsavedChanges.message")/td("unsavedChanges.detail"), ensure the icon-only
close button (X) has an accessible name (aria-label or use Dialog.Close) and
keep the existing action buttons calling onSaveAndClose and onDiscardAndClose;
preserve styling/classes but remove the custom focus/escape logic in favor of
the Dialog primitive for proper a11y and focus trapping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccb4cf52-1a68-4d61-a627-f656c6b1f15f

📥 Commits

Reviewing files that changed from the base of the PR and between 884021c and b3469c4.

📒 Files selected for processing (5)
  • electron/electron-env.d.ts
  • electron/main.ts
  • electron/preload.ts
  • src/components/video-editor/UnsavedChangesDialog.tsx
  • src/components/video-editor/VideoEditor.tsx

Comment thread electron/main.ts
Comment thread src/components/video-editor/UnsavedChangesDialog.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
electron/main.ts (1)

298-307: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep isCloseConfirmInFlight set until the save flow actually finishes

On Line 299, the guard is reset before save-before-close-done returns, so a second close click can reopen the confirm flow and queue duplicate save/close work. lowkey risky for double-dispatch.

Suggested patch
-		ipcMain.once("close-confirm-response", (_, choice: "save" | "discard" | "cancel") => {
-			isCloseConfirmInFlight = false;
+		ipcMain.once("close-confirm-response", (_, choice: "save" | "discard" | "cancel") => {
 			if (!windowToClose || windowToClose.isDestroyed()) return;
 
 			if (choice === "save") {
 				// Tell renderer to save the project, then close when done
 				windowToClose.webContents.send("request-save-before-close");
 				ipcMain.once("save-before-close-done", (_, shouldClose: boolean) => {
+					isCloseConfirmInFlight = false;
 					if (!shouldClose) return;
 					forceCloseEditorWindow(windowToClose);
 				});
 			} else if (choice === "discard") {
+				isCloseConfirmInFlight = false;
 				forceCloseEditorWindow(windowToClose);
+			} else {
+				isCloseConfirmInFlight = false;
 			}
 			// "cancel": flag reset, window stays open
 		});
#!/bin/bash
# Verify guard lifecycle around close-confirm/save-before-close flow
rg -n -C4 'isCloseConfirmInFlight|close-confirm-response|save-before-close-done' electron/main.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/main.ts` around lines 298 - 307, The guard isCloseConfirmInFlight is
currently cleared immediately in the close-confirm-response handler allowing a
second close to re-enter while the save flow is in-flight; move or defer
clearing that flag so it remains true until the entire save/close flow
completes: remove the early isCloseConfirmInFlight = false at the top of the
ipcMain.once("close-confirm-response", ...) handler and instead clear
isCloseConfirmInFlight only inside each terminal branch of the flow (after
forceCloseEditorWindow when choice === "save" and after handling
discard/cancel), and also clear it inside the
ipcMain.once("save-before-close-done", ...) callback after handling shouldClose
to ensure the flag covers the full save-before-close lifetime; reference:
isCloseConfirmInFlight, ipcMain.once("close-confirm-response"),
ipcMain.once("save-before-close-done"), windowToClose, forceCloseEditorWindow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@electron/main.ts`:
- Around line 298-305: The IPC handlers for "close-confirm-response" and
"save-before-close-done" are currently using ipcMain.once and will consume the
first matching message from any renderer, so scope them to the originating
window by switching to named listeners that check event.sender.id against
windowToClose.webContents.id and only act when they match; implement
ipcMain.on("close-confirm-response", handler) and
ipcMain.on("save-before-close-done", handler) where each handler does if
(!windowToClose || windowToClose.isDestroyed()) { cleanup/remove listener } else
if (event.sender.id !== windowToClose.webContents.id) return; else perform the
existing logic (set isCloseConfirmInFlight, send "request-save-before-close",
handle shouldClose) and remove the listener after handling to avoid leaks.
Ensure you reference the existing variables windowToClose and
isCloseConfirmInFlight and remove the listener if the window is closed or after
a successful match.

---

Duplicate comments:
In `@electron/main.ts`:
- Around line 298-307: The guard isCloseConfirmInFlight is currently cleared
immediately in the close-confirm-response handler allowing a second close to
re-enter while the save flow is in-flight; move or defer clearing that flag so
it remains true until the entire save/close flow completes: remove the early
isCloseConfirmInFlight = false at the top of the
ipcMain.once("close-confirm-response", ...) handler and instead clear
isCloseConfirmInFlight only inside each terminal branch of the flow (after
forceCloseEditorWindow when choice === "save" and after handling
discard/cancel), and also clear it inside the
ipcMain.once("save-before-close-done", ...) callback after handling shouldClose
to ensure the flag covers the full save-before-close lifetime; reference:
isCloseConfirmInFlight, ipcMain.once("close-confirm-response"),
ipcMain.once("save-before-close-done"), windowToClose, forceCloseEditorWindow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2963b14c-9402-415e-b91b-a7c567b37d7c

📥 Commits

Reviewing files that changed from the base of the PR and between b3469c4 and 36076aa.

📒 Files selected for processing (2)
  • electron/main.ts
  • src/components/video-editor/UnsavedChangesDialog.tsx

Comment thread electron/main.ts Outdated
makaradam added 3 commits May 2, 2026 13:43
./openscreen.png resolves correctly both in dev (Vite serves public/)
and in production (loadFile sets base to dist/, where public assets land
inside the asar). getAssetPath points to extraResources, which is the
wrong location for bundled dist assets.
Both ipcMain.once handlers now check event.sender.id against
windowToClose.webContents.id and ignore messages from any other
renderer, preventing cross-window response mix-ups if multiple editor
windows are ever open simultaneously.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: replace native OS close dialog with custom in-app dialog

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.