Skip to content

feat:add countdown before record start#460

Merged
siddharthvaddem merged 10 commits intosiddharthvaddem:mainfrom
Galactic99:feat/countdown-before-record-start
Apr 20, 2026
Merged

feat:add countdown before record start#460
siddharthvaddem merged 10 commits intosiddharthvaddem:mainfrom
Galactic99:feat/countdown-before-record-start

Conversation

@Galactic99
Copy link
Copy Markdown
Contributor

@Galactic99 Galactic99 commented Apr 16, 2026

Description

Implemented a dedicated centered countdown overlay before recording start while keeping the HUD visible and stationary at the bottom.

Key implementation details:

  • Added a separate transparent always-on-top countdown overlay window.
  • Routed a new window type for countdown rendering.
  • Added IPC flow to show, update and hide countdown values.
  • Updated recording flow to run 3-2-1 countdown before start including cancellation handling and race-safety guards.
  • Reused countdown overlay window by hiding it instead of closing it to reduce churn/flicker.
  • Added startup/show timing fixes to reduce first-paint flicker and keep HUD visibility correct.
  • You can cancel the countdown by clicking on the record button while the countdown timer is running

Motivation

Helps the user to prepare themselves before recording actually begins.

Type of Change

  • New Feature

Related Issue(s)

#458 #453

Screenshots / Video

Video :

After-Countdown-Timer.mp4

Testing

  1. Start app.
  2. Verify HUD appears immediately on launch.
  3. Select a recording source.
  4. Click Record and verify centered 3-2-1 countdown overlay appears while HUD remains visible.
  5. Verify recording starts after countdown ends.
  6. Cancel during countdown and verify countdown hides cleanly and no recording starts.
  7. Repeat multiple times to verify no stuck overlay and no race behavior.
  8. Confirm tray interactions still work with HUD/recording flow.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Thank you for contributing!

Summary by CodeRabbit

  • New Features

    • Added a centered, transparent countdown overlay (3→2→1) before screen recording: always‑on‑top, non‑interactive, macOS Spaces‑aware, and controllable (show/update/hide). Overlay appears as its own window type and renders when active.
    • Exposed a subscription API so the app UI receives live countdown value updates.
  • Bug Fixes / Reliability

    • Improved countdown lifecycle, cancellation and validation to prevent stray or partial recordings; recording now waits for countdown completion and cleans up reliably.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Caution

Review failed

Pull request was closed or merged during review

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
📝 Walkthrough

Walkthrough

Adds a countdown-overlay feature across Electron and renderer: new preload/type defs, IPC handlers and overlay window, renderer CountdownOverlay component, and countdown orchestration integrated into the screen-recording hook with run-id based cancellation and safe IPC wrappers.

Changes

Cohort / File(s) Summary
Type defs & Preload
electron/electron-env.d.ts, electron/preload.ts
Expose showCountdownOverlay, setCountdownOverlayValue, hideCountdownOverlay, and onCountdownOverlayValue on window.electronAPI (invoke + subscription/unsubscribe).
IPC handlers
electron/ipc/handlers.ts
Add overlay state (visible, value, activeRunId), debounced-hide logic, flushCountdownOverlayState, and IPC channels: "countdown-overlay-show", "countdown-overlay-set-value", "countdown-overlay-hide". registerIpcHandlers accepts create/get countdown window callbacks; some error-scoping and handler tweaks.
Main process & window factory
electron/main.ts, electron/windows.ts
New createCountdownOverlayWindow() (420×260, transparent, frameless, always-on-top, ignore mouse). main.ts keeps a wrapper reference, reuses window, updates macOS activate logic to ignore overlay windows.
Renderer entry & App
src/main.tsx, src/App.tsx, src/components/launch/CountdownOverlay.tsx
Parse windowType from URL; set transparent backgrounds for overlay types; render new <CountdownOverlay /> for windowType==="countdown-overlay"; component subscribes to overlay values and displays the countdown UI.
Recording hook
src/hooks/useScreenRecorder.ts
Integrate countdown orchestration with countdownRunId/countdownActive, safe IPC wrappers (try/catch + warn), start/cancel countdown flow with run-token validation, and adapt recording start/teardown to respect countdown cancellation.
Minor
electron/ipc/... (other small edits), electron/main.ts (activate flow tweak)
Supplemental tweaks: start-new-recording handler async->sync change, read-binary-file error response cleanup, and small scoping fixes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • siddharthvaddem

Poem

three… two… one — a tiny frameless glow,
ipc hums softly, numbers come and go.
run‑ids keep the promise, cancel if it’s cursed,
nit: cleaner timers, lowkey risky but rehearsed.
✨🕒

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding a countdown before recording starts, which is the core purpose of this PR.
Description check ✅ Passed The description covers all required sections: purpose, motivation, type of change, related issues, testing steps, and a demo video. Well-structured and complete.

✏️ 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

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: 6526f82776

ℹ️ 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 src/hooks/useScreenRecorder.ts Outdated
Comment thread electron/ipc/handlers.ts
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: 4

🤖 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/ipc/handlers.ts`:
- Around line 362-397: Maintain a small main-process state object (e.g., {
visible, value }) for the countdown overlay and update it from the ipc handlers:
when handling "countdown-overlay-show" (in the ipcMain.handle block that uses
getCountdownOverlayWindow/createCountdownOverlayWindow) set state.visible = true
and state.value = value, create the window if needed, and if
win.webContents.isLoading() attach a did-finish-load listener that, when fired
and win still exists, sends only the latest state via
win.webContents.send("countdown-overlay-value", state.value) and calls
win.showInactive() only if state.visible is still true; similarly, make
"countdown-overlay-set-value" update state.value and send immediately only if
the webContents is not loading, and make "countdown-overlay-hide" set
state.visible = false and either hide immediately (if not loading) or prevent
the did-finish-load handler from showing the window by relying on the updated
state.

In `@electron/main.ts`:
- Around line 331-341: The hidden countdown overlay is keeping getAllWindows() >
0 and breaking app activation; update the app.on("activate") logic to ignore the
overlay window instead of counting it: when deciding to recreate windows, filter
BrowserWindow.getAllWindows() to only visible/non-destroyed windows and exclude
the countdownOverlayWindow (refer to createCountdownOverlayWindowWrapper and the
countdownOverlayWindow variable) — if that filtered list is empty then recreate
the HUD/editor as before. Ensure you check w.isVisible() and !w.isDestroyed()
(or w !== countdownOverlayWindow) so the overlay doesn't prevent activation.

In `@electron/windows.ts`:
- Around line 190-223: The overlay BrowserWindow is created with show: !HEADLESS
which lets it paint before the renderer/first IPC value arrives; change the
BrowserWindow creation in windows.ts (the win = new BrowserWindow({...}) block)
to create the window hidden (show: false) and remove the HEADLESS-based
immediate show, then call win.show() from the existing IPC-ready path in
electron/ipc/handlers.ts (the handler flow around lines 362-379 that sends the
first value) once the renderer signals it's fully loaded/ready — ensure you
expose the BrowserWindow instance or send an IPC "ready-to-show" message from
the renderer and invoke win.show() only after that handshake to prevent the
blank flash.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 404-451: The cancel path can race with the async setup in
startRecordCountdown/startRecording: thread the current run id into
startRecording (pass runId from startRecordCountdown into startRecording) and
inside startRecording re-check countdownRunId.current === runId after every
await (e.g., after getSelectedSource, getUserMedia, any async device/track
setup) and abort if it changed; when aborting, ensure you stop and release any
acquired MediaStreamTracks and hide overlays/cleanup before returning. Update
cancelCountdown to continue bumping countdownRunId but rely on the guarded
checks in startRecording (which now accepts runId) to prevent recording from
starting after cancel.
🪄 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: 3808dbb7-f9f0-4ced-8eb2-d4596b91caab

📥 Commits

Reviewing files that changed from the base of the PR and between 6d449a4 and 6526f82.

📒 Files selected for processing (9)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/App.tsx
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
  • src/main.tsx

Comment thread electron/ipc/handlers.ts Outdated
Comment thread electron/main.ts
Comment thread electron/windows.ts
Comment thread src/hooks/useScreenRecorder.ts
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

🧹 Nitpick comments (2)
src/hooks/useScreenRecorder.ts (2)

340-341: nit: inconsistent use of safe wrapper

the cleanup effect calls window.electronAPI.hideCountdownOverlay() directly, while line 452 uses safeHideCountdownOverlay(). if the IPC call throws during unmount, it could cause issues. might be cleaner to use the safe wrapper here too.

♻️ Suggested change
 		countdownRunId.current += 1;
-		void window.electronAPI.hideCountdownOverlay();
+		void safeHideCountdownOverlay();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 340 - 341, In the cleanup
effect, replace the direct IPC call window.electronAPI.hideCountdownOverlay()
with the safe wrapper safeHideCountdownOverlay() (same one used at line 452) to
avoid throwing during unmount; update the cleanup to call
safeHideCountdownOverlay() and keep incrementing countdownRunId.current as-is so
the unmount path is protected by the existing safe IPC wrapper.

419-422: kinda old-school with the alert() but it works

this matches the existing pattern at line 461. might be worth converting both to toast notifications eventually for UX consistency, but not a blocker.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 419 - 422, In useScreenRecorder,
avoid using alert(...) for the selectedSource check; replace the
alert(t("recording.selectSource")) call with the same toast/notification
mechanism used elsewhere in this hook (the other validation branch) so the UX is
consistent, keep the translation key t("recording.selectSource") and the early
return behavior tied to selectedSource.
🤖 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/ipc/handlers.ts`:
- Around line 417-429: The hide handler for the countdown overlay
(ipcMain.handle "countdown-overlay-hide") only sets
countdownOverlayState.visible = false and clears countdownOverlayState.value but
never actually hides the BrowserWindow; update the handler that uses
getCountdownOverlayWindow() and win.webContents.send("countdown-overlay-value",
...) to call win.hide() after sending the null value (mirroring the show
handler's use of win.showInactive()) so the overlay window is actually hidden
rather than just emptied.

---

Nitpick comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 340-341: In the cleanup effect, replace the direct IPC call
window.electronAPI.hideCountdownOverlay() with the safe wrapper
safeHideCountdownOverlay() (same one used at line 452) to avoid throwing during
unmount; update the cleanup to call safeHideCountdownOverlay() and keep
incrementing countdownRunId.current as-is so the unmount path is protected by
the existing safe IPC wrapper.
- Around line 419-422: In useScreenRecorder, avoid using alert(...) for the
selectedSource check; replace the alert(t("recording.selectSource")) call with
the same toast/notification mechanism used elsewhere in this hook (the other
validation branch) so the UX is consistent, keep the translation key
t("recording.selectSource") and the early return behavior tied to
selectedSource.
🪄 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: ad152987-5c97-4cdc-94d5-039085c5ccd6

📥 Commits

Reviewing files that changed from the base of the PR and between 6526f82 and c10441f.

📒 Files selected for processing (7)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/launch/CountdownOverlay.tsx
  • electron/electron-env.d.ts
  • electron/windows.ts

Comment thread electron/ipc/handlers.ts Outdated
@Galactic99
Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

✅ Actions performed

Full review triggered.

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

♻️ Duplicate comments (1)
src/hooks/useScreenRecorder.ts (1)

604-639: ⚠️ Potential issue | 🟠 Major

There’s still one async cancel gap in startRecording().

applyConstraints() at Line 605 is awaited, but the next run-id check does not happen until Line 636. If the user cancels during that constraint negotiation, this path still falls through and starts the recorder anyway. same race, just one await later.

small guard, big difference
 			try {
 				await videoTrack.applyConstraints({
 					frameRate: { ideal: TARGET_FRAME_RATE, max: TARGET_FRAME_RATE },
 					width: { ideal: TARGET_WIDTH, max: TARGET_WIDTH },
 					height: { ideal: TARGET_HEIGHT, max: TARGET_HEIGHT },
 				});
 			} catch (constraintError) {
 				console.warn(
 					"Unable to lock 4K/60fps constraints, using best available track settings.",
 					constraintError,
 				);
 			}
+
+			if (!isCountdownRunActive(countdownRunToken)) {
+				teardownMedia();
+				return;
+			}
 
 			let {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 604 - 639, startRecording() can
race: after awaiting videoTrack.applyConstraints(...) the code may proceed even
if the run was cancelled; add an immediate cancellation guard right after the
try/catch (and before calling
videoTrack.getSettings()/computeBitrate/selectMimeType) by calling
isCountdownRunActive(countdownRunToken) and, if false, call teardownMedia() and
return; reference applyConstraints, isCountdownRunActive, countdownRunToken,
teardownMedia, and startRecording so you place the early-return check in the
correct spot.
🧹 Nitpick comments (1)
electron/ipc/handlers.ts (1)

382-400: looks good, small nit on potential listener stacking

If countdown-overlay-show is called multiple times while the window is still loading, you'll stack up multiple did-finish-load listeners (each using .once()). When load completes, all of them fire and flushCountdownOverlayState runs multiple times. It's harmless - just redundant sends of the same value - but if you want to be tidy:

// track pending listener
let pendingFlush = false;

// in handler:
if (win.webContents.isLoading()) {
    if (!pendingFlush) {
        pendingFlush = true;
        win.webContents.once("did-finish-load", () => {
            pendingFlush = false;
            if (!win.isDestroyed()) {
                flushCountdownOverlayState(win);
            }
        });
    }
} else {
    flushCountdownOverlayState(win);
}

lowkey optional though - the current approach works fine, just chatty.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 382 - 400, Multiple calls to the
ipcMain handler "countdown-overlay-show" can register multiple
win.webContents.once("did-finish-load", ...) listeners causing redundant
flushCountdownOverlayState calls; fix it by adding a module-scoped boolean flag
(e.g., pendingCountdownFlush) and use it inside the handler that references
getCountdownOverlayWindow/createCountdownOverlayWindow: when
win.webContents.isLoading() only attach the once listener if
pendingCountdownFlush is false, set pendingCountdownFlush = true when attaching,
and clear it inside the once callback before calling flushCountdownOverlayState
(also guard with win.isDestroyed()), so repeated calls while loading will no-op
until the load finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/CountdownOverlay.tsx`:
- Around line 15-19: The countdown digits in CountdownOverlay (the div with
className "text-white/90 text-[120px] font-bold leading-none tabular-nums") need
better contrast; add a subtle contrast treatment such as a small text-shadow (or
a semi-transparent dark outline/background) to the digits so they remain
readable on bright/translucent backgrounds—update the className or style for
that element (or its wrapper) to include the chosen shadow/outline/backdrop
utility (e.g., add a faint text-shadow or bg-black/20 with rounded padding)
while preserving sizing and pointer-events-none on the outer container.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 428-453: The countdown overlay paint race occurs because IPC
awaits (safeShowCountdownOverlay / safeSetCountdownOverlayValue) can resolve
after the run was cancelled and repaint; to fix it, after every await of
safeShowCountdownOverlay and safeSetCountdownOverlayValue check that
countdownRunId.current === runId before performing any UI updates (i.e., only
call safeSetCountdownOverlayValue or proceed to next steps when still the
current run), and change the finally block to always call
safeHideCountdownOverlay (remove the conditional guard around
safeHideCountdownOverlay) while still only calling setCountdownActive(false)
when countdownRunId.current === runId if you need that guard; reference
functions/variables: safeShowCountdownOverlay, safeSetCountdownOverlayValue,
safeHideCountdownOverlay, countdownRunId, runId, startRecording,
setCountdownActive.

---

Duplicate comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 604-639: startRecording() can race: after awaiting
videoTrack.applyConstraints(...) the code may proceed even if the run was
cancelled; add an immediate cancellation guard right after the try/catch (and
before calling videoTrack.getSettings()/computeBitrate/selectMimeType) by
calling isCountdownRunActive(countdownRunToken) and, if false, call
teardownMedia() and return; reference applyConstraints, isCountdownRunActive,
countdownRunToken, teardownMedia, and startRecording so you place the
early-return check in the correct spot.

---

Nitpick comments:
In `@electron/ipc/handlers.ts`:
- Around line 382-400: Multiple calls to the ipcMain handler
"countdown-overlay-show" can register multiple
win.webContents.once("did-finish-load", ...) listeners causing redundant
flushCountdownOverlayState calls; fix it by adding a module-scoped boolean flag
(e.g., pendingCountdownFlush) and use it inside the handler that references
getCountdownOverlayWindow/createCountdownOverlayWindow: when
win.webContents.isLoading() only attach the once listener if
pendingCountdownFlush is false, set pendingCountdownFlush = true when attaching,
and clear it inside the once callback before calling flushCountdownOverlayState
(also guard with win.isDestroyed()), so repeated calls while loading will no-op
until the load finishes.
🪄 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: cb995814-d481-4616-85cc-62294934d399

📥 Commits

Reviewing files that changed from the base of the PR and between 6d449a4 and c10441f.

📒 Files selected for processing (9)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/App.tsx
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
  • src/main.tsx

Comment thread src/components/launch/CountdownOverlay.tsx
Comment thread src/hooks/useScreenRecorder.ts
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

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

Inline comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 761-767: The current toggleRecording logic checks countdownActive
before recording, which can let a click intended to stop an already-started
recording cancel the countdown instead; change the branching to prioritize
active recording first: if recording then call stopRecording.current(), else if
countdownActive then call cancelCountdown(), else call startRecordCountdown();
apply the same reorder to the duplicate handler that uses recording,
countdownActive, cancelCountdown and startRecordCountdown (the block around
lines 829-833) so both handlers consistently prioritize stopping an active
recording.
🪄 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: 25789ab7-9c5e-43af-819d-63ee8cc5a984

📥 Commits

Reviewing files that changed from the base of the PR and between c10441f and f548bef.

📒 Files selected for processing (5)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/preload.ts
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/launch/CountdownOverlay.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • electron/electron-env.d.ts

Comment thread src/hooks/useScreenRecorder.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: 1

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

Inline comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 339-343: The direct calls to
window.electronAPI.hideCountdownOverlay in the cleanup/cancel paths (around
countdownRunId.current and allowAutoFinalize.current) can reject and are
currently uncaught; replace these direct awaits/calls with the existing safe
wrapper used elsewhere in this module (the module-level safe invoke used for
electronAPI calls) so rejections are swallowed/handled, and apply the same
change to the other occurrence around lines 373–378; ensure you pass the
activeRunId (countdownRunId.current) into the safe wrapper instead of calling
hideCountdownOverlay directly.
🪄 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: 219635e7-5ff2-4cb0-92fc-4ef7074a3c39

📥 Commits

Reviewing files that changed from the base of the PR and between f548bef and 3cadf9f.

📒 Files selected for processing (1)
  • src/hooks/useScreenRecorder.ts

Comment thread src/hooks/useScreenRecorder.ts
@Galactic99
Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

✅ Actions performed

Full review triggered.

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

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

Inline comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 418-432: The issue is that the code only reserves a countdownRunId
after awaiting getSelectedSource(), leaving a race where another invocation can
claim the run; to fix, claim and store the runId before any await by
incrementing countdownRunId.current and assigning runId at the top of
startRecordCountdown (before calling window.electronAPI.getSelectedSource()),
then proceed to await getSelectedSource() and only call setCountdownActive(true)
after you verify selectedSource is non-null; keep the early-return behavior if
no source is selected (no need to call setCountdownActive in that case).
🪄 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: e9e36a68-9d5d-4855-958f-7a61f0ace72a

📥 Commits

Reviewing files that changed from the base of the PR and between 6d449a4 and 3845739.

📒 Files selected for processing (9)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/App.tsx
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
  • src/main.tsx

Comment thread src/hooks/useScreenRecorder.ts Outdated
@Galactic99
Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

✅ Actions performed

Full review triggered.

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.

🧹 Nitpick comments (2)
src/App.tsx (1)

17-33: nit: dependency array might be over-specified

The [windowType] dependency causes the effect to re-run whenever windowType changes, but since windowType is derived from the URL (which doesn't change for a given window), the effect only really needs to run once on mount. This also means loadAllCustomFonts() gets called again if state somehow changes.

lowkey harmless since the window type won't change during the overlay's lifetime, but [] would be cleaner if the intent is mount-only initialization.

nit: cleaner dependency array
-	}, [windowType]);
+	}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 17 - 33, The effect in useEffect currently depends
on [windowType], causing it to re-run when windowType changes even though
windowType is derived once from the URL; change the dependency array to [] so
the effect runs only on mount, keep the same logic that reads const type = new
URLSearchParams(window.location.search).get("windowType") || "" and calls
setWindowType(type) if different, and still call
loadAllCustomFonts().catch(...); if your linter complains about exhaustive-deps,
add an inline eslint-disable-next-line comment for that rule scoped to this
useEffect.
electron/ipc/handlers.ts (1)

362-439: Would add one regression test for the loading path.

lowkey this block is where the race fix lives now. a focused test for show(3)setValue(2/1) and hide() before did-finish-load would make the cancel/restart behavior a lot less likely to get kinda cursed again later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 362 - 439, Add a regression test that
exercises the loading path for the countdown overlay: simulate calling the IPC
handler "countdown-overlay-show" with value 3, then call
"countdown-overlay-set-value" with 2 and 1 and finally "countdown-overlay-hide"
all before the window emits "did-finish-load"; verify that when the window
finishes loading (did-finish-load) the flushCountdownOverlayState/flush behavior
uses the latest state (visible false/value null if hidden, or the last set value
if still visible) and that no stale or duplicate sends occur. Target the
handlers "countdown-overlay-show", "countdown-overlay-set-value", and
"countdown-overlay-hide" and exercise the win.webContents.isLoading branch by
creating a mocked countdown window via
getCountdownOverlayWindow/createCountdownOverlayWindow and controlling
did-finish-load to assert correct sends to "countdown-overlay-value".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@electron/ipc/handlers.ts`:
- Around line 362-439: Add a regression test that exercises the loading path for
the countdown overlay: simulate calling the IPC handler "countdown-overlay-show"
with value 3, then call "countdown-overlay-set-value" with 2 and 1 and finally
"countdown-overlay-hide" all before the window emits "did-finish-load"; verify
that when the window finishes loading (did-finish-load) the
flushCountdownOverlayState/flush behavior uses the latest state (visible
false/value null if hidden, or the last set value if still visible) and that no
stale or duplicate sends occur. Target the handlers "countdown-overlay-show",
"countdown-overlay-set-value", and "countdown-overlay-hide" and exercise the
win.webContents.isLoading branch by creating a mocked countdown window via
getCountdownOverlayWindow/createCountdownOverlayWindow and controlling
did-finish-load to assert correct sends to "countdown-overlay-value".

In `@src/App.tsx`:
- Around line 17-33: The effect in useEffect currently depends on [windowType],
causing it to re-run when windowType changes even though windowType is derived
once from the URL; change the dependency array to [] so the effect runs only on
mount, keep the same logic that reads const type = new
URLSearchParams(window.location.search).get("windowType") || "" and calls
setWindowType(type) if different, and still call
loadAllCustomFonts().catch(...); if your linter complains about exhaustive-deps,
add an inline eslint-disable-next-line comment for that rule scoped to this
useEffect.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f0c04f99-3cea-4b4d-82ac-f06c9e445502

📥 Commits

Reviewing files that changed from the base of the PR and between 6d449a4 and 42dfe05.

📒 Files selected for processing (9)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/App.tsx
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
  • src/main.tsx

@Galactic99
Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Collaborator

@imAaryash imAaryash left a comment

Choose a reason for hiding this comment

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

Hii @Galactic99 , its a great addon, could you make the countdown's UI a bit better as its getting mixed with the screen maybe add something like a low opacity circle BG to the countdown :)

Image

@Galactic99
Copy link
Copy Markdown
Contributor Author

@imAaryash Thank you for the suggestion. Will implement it right away.

@Galactic99
Copy link
Copy Markdown
Contributor Author

GitHub Dark Mode
image

White Background
image

Dark Background
image

Black Background
image

@imAaryash Is this alright? Can I proceed with the changes?

@imAaryash
Copy link
Copy Markdown
Collaborator

GitHub Dark Mode image

White Background image

Dark Background image

Black Background image

@imAaryash Is this alright? Can I proceed with the changes?

looks good

@Galactic99 Galactic99 requested a review from imAaryash April 17, 2026 05:28
Comment thread electron/ipc/handlers.ts Outdated
Comment thread src/hooks/useScreenRecorder.ts
Comment thread src/hooks/useScreenRecorder.ts
@siddharthvaddem
Copy link
Copy Markdown
Owner

is this ready for another review? @Galactic99

@Galactic99
Copy link
Copy Markdown
Contributor Author

No @siddharthvaddem, marking this PR as draft for now.

@Galactic99 Galactic99 marked this pull request as draft April 18, 2026 18:10
@Galactic99 Galactic99 force-pushed the feat/countdown-before-record-start branch from b9e1c16 to 7e02856 Compare April 19, 2026 07:09
@Galactic99 Galactic99 marked this pull request as ready for review April 19, 2026 07:09
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.

🧹 Nitpick comments (2)
src/hooks/useScreenRecorder.ts (1)

373-380: nit: use the safeHideCountdownOverlay wrapper for consistency

every other call site (cleanup at L342, the countdown loop finally at L471) goes through the safeHideCountdownOverlay wrapper. this one opens its own inline .catch(...) which works identically but makes the codebase read a bit inconsistent. kinda cursed to have two patterns for the exact same thing.

♻️ tiny cleanup
 	const cancelCountdown = () => {
 		const activeRunId = countdownRunId.current;
 		countdownRunId.current += 1;
 		setCountdownActive(false);
-		void window.electronAPI.hideCountdownOverlay(activeRunId).catch((error) => {
-			console.warn("Failed to hide countdown overlay during cancel:", error);
-		});
+		void safeHideCountdownOverlay(activeRunId);
 	};

(note: you'll need to hoist safeHideCountdownOverlay above cancelCountdown, or forward-reference it via a ref/useCallback, since it's currently declared a few lines below. easier fix is to just reorder the declarations.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 373 - 380, The cancelCountdown
function currently calls window.electronAPI.hideCountdownOverlay with an inline
.catch handler; replace that inline call with the existing
safeHideCountdownOverlay wrapper for consistency (pass the same activeRunId),
and ensure safeHideCountdownOverlay is declared before cancelCountdown (or
forward-reference it via a ref/useCallback) so cancelCountdown can call
safeHideCountdownOverlay(activeRunId); keep the rest of cancelCountdown logic
(incrementing countdownRunId.current and setCountdownActive(false)) unchanged.
src/App.tsx (1)

17-33: nit: font loader is now roped into the windowType dep

changing the dep from [] to [windowType] means loadAllCustomFonts() also re-fires whenever windowType flips. in practice this only runs twice (initial "" → parsed value), so it's lowkey harmless, but it's kinda a side effect you probably didn't mean to re-trigger. worth splitting into two effects so the transparent-background/query-sync logic is dep-driven while font loading stays mount-only.

♻️ suggested split
 	useEffect(() => {
 		const type = new URLSearchParams(window.location.search).get("windowType") || "";
 		if (type !== windowType) {
 			setWindowType(type);
 		}
 
 		if (type === "hud-overlay" || type === "source-selector" || type === "countdown-overlay") {
 			document.body.style.background = "transparent";
 			document.documentElement.style.background = "transparent";
 			document.getElementById("root")?.style.setProperty("background", "transparent");
 		}
-
-		// Load custom fonts on app initialization
-		loadAllCustomFonts().catch((error) => {
-			console.error("Failed to load custom fonts:", error);
-		});
 	}, [windowType]);
+
+	useEffect(() => {
+		// Load custom fonts on app initialization
+		loadAllCustomFonts().catch((error) => {
+			console.error("Failed to load custom fonts:", error);
+		});
+	}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 17 - 33, The current useEffect mixes
windowType-driven behavior with a mount-only side effect (loadAllCustomFonts),
causing fonts to reload whenever windowType changes; split into two effects:
keep the existing effect that reads URL, compares and calls setWindowType and
updates transparent backgrounds inside a useEffect dependent on [windowType],
and create a separate useEffect with an empty dependency array that calls
loadAllCustomFonts().catch(...) so fonts load only once on mount; reference
functions/hooks: useEffect, setWindowType, windowType, and loadAllCustomFonts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/App.tsx`:
- Around line 17-33: The current useEffect mixes windowType-driven behavior with
a mount-only side effect (loadAllCustomFonts), causing fonts to reload whenever
windowType changes; split into two effects: keep the existing effect that reads
URL, compares and calls setWindowType and updates transparent backgrounds inside
a useEffect dependent on [windowType], and create a separate useEffect with an
empty dependency array that calls loadAllCustomFonts().catch(...) so fonts load
only once on mount; reference functions/hooks: useEffect, setWindowType,
windowType, and loadAllCustomFonts.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 373-380: The cancelCountdown function currently calls
window.electronAPI.hideCountdownOverlay with an inline .catch handler; replace
that inline call with the existing safeHideCountdownOverlay wrapper for
consistency (pass the same activeRunId), and ensure safeHideCountdownOverlay is
declared before cancelCountdown (or forward-reference it via a ref/useCallback)
so cancelCountdown can call safeHideCountdownOverlay(activeRunId); keep the rest
of cancelCountdown logic (incrementing countdownRunId.current and
setCountdownActive(false)) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fccc6eda-a599-4b32-ba3a-c344c1e07bf8

📥 Commits

Reviewing files that changed from the base of the PR and between b9e1c16 and 7e02856.

📒 Files selected for processing (9)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/App.tsx
  • src/components/launch/CountdownOverlay.tsx
  • src/hooks/useScreenRecorder.ts
  • src/main.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/launch/CountdownOverlay.tsx
  • src/main.tsx
  • electron/main.ts
  • electron/preload.ts

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: 7e02856836

ℹ️ 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 src/hooks/useScreenRecorder.ts
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.

🧹 Nitpick comments (1)
src/hooks/useScreenRecorder.ts (1)

409-481: countdown orchestration looks solid.

race handling in startRecordCountdown is kinda thorough now — runId is claimed before any await, every post-await resume point re-checks countdownRunId, and the finally block cleans up when we bail before overlayHiddenBeforeStart flips. paired with the per-stage isCountdownRunActive gates in startRecording, cancel/unmount during any phase should land gracefully. nice work untangling this.

one tiny optional nit: the 1s window.setTimeout on Line 459 isn't cancellable, so a cancel during the tick still burns up to a full second before the loop notices and returns. not user-visible (overlay is already hidden by cancelCountdown), but if you ever want snappier teardown, an AbortController-backed sleep would do it. totally fine to punt.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 409 - 481, The one-second
window.setTimeout inside startRecordCountdown can delay teardown; make the
per-tick sleep cancellable by introducing an AbortController tied to the current
runId (e.g., countdownAbortController.current) and replace the hard setTimeout
with an AbortSignal-aware sleep that resolves immediately when aborted; ensure
cancelCountdown/cleanup sets countdownRunId appropriately and aborts the
controller so safeShowCountdownOverlay, safeSetCountdownOverlayValue,
safeHideCountdownOverlay, and the loop in startRecordCountdown detect
cancellation without waiting the full second.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 409-481: The one-second window.setTimeout inside
startRecordCountdown can delay teardown; make the per-tick sleep cancellable by
introducing an AbortController tied to the current runId (e.g.,
countdownAbortController.current) and replace the hard setTimeout with an
AbortSignal-aware sleep that resolves immediately when aborted; ensure
cancelCountdown/cleanup sets countdownRunId appropriately and aborts the
controller so safeShowCountdownOverlay, safeSetCountdownOverlayValue,
safeHideCountdownOverlay, and the loop in startRecordCountdown detect
cancellation without waiting the full second.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e0e0b3f-9a50-4ac1-86ab-0fba8aa99e51

📥 Commits

Reviewing files that changed from the base of the PR and between 7e02856 and 4a65ab8.

📒 Files selected for processing (2)
  • src/App.tsx
  • src/hooks/useScreenRecorder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/App.tsx

@Galactic99 Galactic99 requested a review from imAaryash April 19, 2026 07:41
@imAaryash
Copy link
Copy Markdown
Collaborator

hey, @Galactic99 have you thought of any solution for the flickering?

@Galactic99
Copy link
Copy Markdown
Contributor Author

@imAaryash yes

  1. I added the win.hide() just like you suggested
  2. On hide request, I immediately suppress the overlay visually (opacity(0)) and schedule the native hide with a short debounce.
  3. If a new countdown arrives quickly (the flicker case), the pending hide commit is canceled via token/timer invalidation, so we do not hide-show-hide in rapid succession.
  4. This helps to avoid flickering in Windows. It should also support MacOS.
  5. On Linux (no reliable opacity support), immediate native hiding is used.

@imAaryash
Copy link
Copy Markdown
Collaborator

ohhh great, could you drop the final test video

@Galactic99
Copy link
Copy Markdown
Contributor Author

CountDownBeforeStartFinal.mp4

At first I am showing the non flickering state by rapid start/stop. It is not a bug.
Also the timer does not show up when u pause a recording and resume a recording. I did not want to add a timer there because I thought it would be annoying.

imAaryash
imAaryash previously approved these changes Apr 20, 2026
@siddharthvaddem
Copy link
Copy Markdown
Owner

@Galactic99 please resolve the conflicts before this can be merged

@imAaryash
Copy link
Copy Markdown
Collaborator

done i have resolved it

@siddharthvaddem siddharthvaddem merged commit cccb966 into siddharthvaddem:main Apr 20, 2026
8 of 9 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 21, 2026
4 tasks
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.

3 participants