Skip to content

Add launch global shortcuts to tray and More menu#654

Open
awaismirza wants to merge 2 commits into
webadderallorg:mainfrom
awaismirza:feature/shortcut
Open

Add launch global shortcuts to tray and More menu#654
awaismirza wants to merge 2 commits into
webadderallorg:mainfrom
awaismirza:feature/shortcut

Conversation

@awaismirza
Copy link
Copy Markdown

@awaismirza awaismirza commented Jun 4, 2026

Summary

This branch builds on the launch/global shortcut work already started in feature/shortcut and finishes the user-facing integration.

It adds configurable launch shortcuts for recording controls, wires those controls through the Electron tray and recording state updates, and surfaces the active shortcut bindings directly in the launch window's More menu.

What changed

Launch/global shortcut foundation

  • added a dedicated LaunchShortcutsConfig alongside editor shortcuts
  • defined default launch shortcuts for Start, Stop, Pause, Resume, and Mute / Unmute Microphone
  • added launch shortcut persistence, merge logic, and conflict detection
  • extended the shortcuts dialog so launch shortcuts can be configured separately from editor shortcuts
  • registered launch global shortcuts from ShortcutsContext on macOS
  • added launch shortcut handling in the launch window so actions respect current recording state

Tray and recording-state integration

  • expanded recording state IPC to include paused state
  • added tray recording commands for Start, Pause, Resume, and Stop
  • updated the tray label/menu to reflect paused vs active recording
  • wired renderer recording controls to react to tray commands
  • kept main-process recording state in sync when recording pauses or resumes

Launch More menu improvements

  • added recording-related action rows to the launch More menu
  • showed the matching launch shortcut beside each relevant action
  • made menu actions state-aware for idle vs recording vs paused states
  • preserved existing shortcut behavior, including opening source selection when needed
  • extended dropdown items to support trailing shortcut labels and disabled styling
  • added launch locale strings for the new menu labels

Testing

  • npx vitest run src/components/launch/popovers/MorePopover.test.tsx src/lib/shortcuts.test.ts src/components/launch/hooks/useLaunchWindowActions.test.ts
  • npx tsc --noEmit
    EOF

Summary by CodeRabbit

Release Notes

  • New Features

    • Added global keyboard shortcuts for recording control, available on macOS even when the app is in the background
    • Added pause/resume recording functionality
    • Added microphone mute/unmute toggle in the launch window
    • Enhanced shortcut customization to support both local and global recording shortcuts
  • Improvements

    • Updated localization for all supported languages to reflect new recording and shortcut features

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds global OS-level keyboard shortcut support for recording control on macOS, expands recording state tracking to include a paused flag throughout the entire lifecycle, implements tray-driven recording commands, and integrates keyboard-driven and GUI-driven shortcut controls into the launch window. The changes span Electron type definitions, main-process state tracking, React hooks, UI components, context management, and full localization support across all languages.

Changes

Global Launch Shortcuts and Recording Control

Layer / File(s) Summary
Shortcut Type System and Constants
electron/ipc/shortcutTypes.ts, src/lib/shortcuts.ts, src/lib/shortcuts.test.ts
Defines LAUNCH_SHORTCUT_ACTIONS (startRecording, pauseRecording, resumeRecording, toggleMicrophoneMute), LaunchShortcutsConfig record type, ShortcutBinding with key and modifier flags, conflict-detection findLaunchConflict(), DEFAULT_LAUNCH_SHORTCUTS with key bindings, LAUNCH_SHORTCUT_LABELS for display, and resolvePersistedShortcuts() to normalize both editor and launch configs from persisted data. Tests cover default behavior, backward-compatible editor-only merging, and structured editor/launch merging.
Electron IPC Contracts and Type Definitions
electron/electron-env.d.ts, electron/preload.ts, electron/ipc/handlers.ts
Updates Window.electronAPI.setRecordingState(recording, paused?) to accept optional paused flag. Adds onTrayRecordingCommand callback typed with "start" | "pause" | "resume" | "stop" command. Updates onRecordingStateChanged payload to include paused boolean. Introduces registerLaunchGlobalShortcuts, unregisterLaunchGlobalShortcuts, and onLaunchShortcutTriggered APIs. Updates handler callback signature to include paused parameter.
IPC Recording Handlers and State Broadcast
electron/ipc/register/recording.ts, electron/ipc/recording/mac.ts, electron/ipc/recording/windows.ts
Updates registerRecordingHandlers callback to accept paused parameter. Modifies set-recording-state handler to accept paused (default false) and broadcasts both values. Platform-specific capture lifecycle handlers (macOS and Windows) emit paused: false when recording stops.
Main Process Tray UI and Recording State
electron/main.ts
Adds isRecordingPaused state tracking. Implements sendTrayRecordingCommand() helper that routes tray commands to HUD window (falling back to main window), deferring send until window loads. Updates tray tooltip to show paused vs recording state and selected source. Expands recording tray menu to show Pause/Resume toggle and Stop action. Adds idle tray menu "Start Recording" action. IPC handler updates isRecordingPaused and selectedSourceName from incoming state.
Launch Shortcut Registration Infrastructure
electron/ipc/register/settings.ts
Adds globalShortcut import and launch-shortcut helpers. Implements toElectronAccelerator() to convert ShortcutBinding to Electron accelerator format, unregisterLaunchGlobalShortcuts() for cleanup, and notifyLaunchShortcutTriggered() to emit events to HUD window. Registers will-quit cleanup once per session. Introduces register-launch-global-shortcuts IPC handler that validates config, unregisters previous shortcuts, converts bindings to accelerators, gates to supported platforms, registers handlers with globalShortcut, and returns success/failed registrations. Adds unregister-launch-global-shortcuts handler.
Screen Recorder Hook: Recording State and Tray Commands
src/hooks/useScreenRecorder.ts
Updates all state transitions (start, pause, resume, cancel, stop, interrupt) to call setRecordingState(recording, paused) with both flags instead of single-arg form. Adds useEffect to register onTrayRecordingCommand listener that routes tray commands (start, pause, resume, stop) to toggleRecording, pauseRecording, resumeRecording, and stopRecording callbacks with proper cleanup.
Launch Shortcuts Hook and Keyboard Handling
src/components/launch/hooks/useLaunchShortcuts.ts
Implements useLaunchShortcuts hook that accepts launch config and current recording state. Creates runLaunchShortcut dispatcher that switches over action types with state guards (start blocked when recording or countdown active; pause/resume/mute guarded by recording/paused state). Registers window keydown listener that matches pressed keys via matchesShortcut, prevents default when matched, and dispatches to runLaunchShortcut. Subscribes to window.electronAPI.onLaunchShortcutTriggered and routes Electron-triggered shortcuts through same dispatcher. Cleans up listeners on unmount.
Launch Window Integration and Default Sources
src/App.tsx, src/components/launch/LaunchWindow.tsx, src/components/launch/hooks/useLaunchWindowActions.ts, src/components/launch/hooks/useLaunchWindowSystemState.ts
Wraps launch window in ShortcutsProvider. Updates useEffect dependency from [] to [isMacOS]. Reads launchShortcuts and isMac from useShortcuts. Adds toggleMicrophoneMute callback and passes into RecordingControls. Wires useLaunchShortcuts with recording state and control actions. Passes recording state and callbacks to MorePopover. Adds getDefaultLaunchSource() utility and mount-time effect to auto-select default screen when none selected. Skips startup permissions prep in development via SHOULD_PREPARE_PERMISSIONS_ON_STARTUP guard.
MorePopover Recording Controls
src/components/launch/popovers/MorePopover.tsx, src/components/launch/popovers/MorePopover.test.tsx, src/components/launch/popovers/PopoverScaffold.tsx, src/components/launch/LaunchWindow.module.css
Expands MorePopover to accept recording state (recording, paused, countdownActive) and callbacks (onStartOrOpenSources, onStopRecording, onPauseRecording, onResumeRecording, onToggleMicrophoneMute). Conditionally renders recording menu (Pause/Resume, Stop, Mute/Unmute) when recording, or start-recording menu when idle (disabled during countdown). Introduces closePopover() helper for consistent popover-close behavior across all menu actions. Updates DropdownItem to accept optional disabled prop with styling for disabled state. Adds comprehensive test coverage validating menu visibility, state-aware shortcut formatting (including macOS symbols), and interaction behavior.
Shortcuts Configuration Dialog
src/components/video-editor/ShortcutsConfigDialog.tsx
Extends dialog to manage both local editor shortcuts and global launch shortcuts with scoped capture state. Adds launchDraft state tracking and introduces captureTarget (local vs global) and unified conflict object with scope awareness. Key-capture handler branches by scope, using findConflict or findLaunchConflict and updating appropriate draft. Swap conflict resolution updates the scoped draft. Save/reset behavior persists both configs together (persistShortcuts(draft, launchDraft)) and restores both defaults. UI reflects scoped state with separate local and global sections, each with conflict display and capture controls using LAUNCH_SHORTCUT_LABELS.
Shortcuts Context and Global Registration
src/contexts/ShortcutsContext.tsx
Expands ShortcutsContextValue to include launchShortcuts and setLaunchShortcuts. Updates persistShortcuts to accept optional editor and launch configs. Initial effect loads persisted data via resolvePersistedShortcuts, setting both editor and launch shortcuts. Adds macOS-only effect that registers global launch shortcuts via Electron when launchShortcuts changes, with cleanup unregistering on change/unmount. Updates context memo to include launch-shortcut fields.
Localization Support
src/i18n/locales/*/dialogs.json, src/i18n/locales/*/launch.json
Adds translation keys across all 10 supported locales (en, es, fr, it, ko, nl, pt-BR, ru, zh-CN, zh-TW) for recording.startRecording, recording.toggleMicrophoneMute, and shortcut config labels (shortcutsConfig.localShortcuts, globalShortcuts, globalDescription).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Keyboard as Window Keyboard
  participant Electron as Electron Main
  participant Recorder as useScreenRecorder
  participant HUD as HUD Window
  User->>Keyboard: Press Ctrl+Shift+R (Start)
  Keyboard->>HUD: useLaunchShortcuts detects key
  HUD->>Recorder: toggleRecording() if not recording
  Recorder->>Electron: setRecordingState(true, false)
  Electron->>HUD: onRecordingStateChanged({recording:true, paused:false})
  Electron->>Electron: Update Tray Menu
  User->>Electron: Click Pause on Tray
  Electron->>HUD: sendTrayRecordingCommand('pause')
  HUD->>Recorder: pauseRecording()
  Recorder->>Electron: setRecordingState(true, true)
  Electron->>HUD: onRecordingStateChanged({recording:true, paused:true})
  Electron->>Electron: Update Tray Menu (Resume shown)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • webadderallorg/Recordly#399: Both PRs expand the recording pause/resume flow—this PR extends the setRecordingState contract to include paused state and propagates it through Electron IPC, while the referenced PR uses that pause/resume timing to synchronize cursor capture state.
  • webadderallorg/Recordly#488: Both PRs add and update Italian i18n resources (src/i18n/locales/it/*), including launch and shortcut config strings in the same locale files.
  • webadderallorg/Recordly#431: Both PRs build on the HUD/launch-window integration—the referenced PR adds hudOverlayRendererReady Electron API support while this PR extends launch-window plumbing with recording controls and keyboard shortcut handling.

Suggested labels

Checked

Suggested reviewers

  • webadderall

Poem

🐰 Hop into shortcuts, a global delight!
Pause, resume, mute—keyboard takes flight,
State flows through Electron, tray beams with pride,
While context keeps configs synchronized wide.

🚥 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
Description check ✅ Passed The PR description comprehensively covers the key changes across launch shortcuts, tray integration, and menu improvements. While it deviates from the template structure, it provides sufficient detail about objectives, implementation, and testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title 'Add launch global shortcuts to tray and More menu' clearly and concisely summarizes the main change: integrating launch/global shortcuts into the tray and More menu components.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@awaismirza awaismirza changed the title Feature/shortcut Add launch global shortcuts to tray and More menu Jun 4, 2026
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: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
electron/ipc/recording/mac.ts (1)

166-175: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Unexpected helper exits still bypass the tray state pipeline.

Line 169 broadcasts the renderer event, but this path never invokes the onRecordingStateChange callback that electron/main.ts uses at Lines 1014-1019 to refresh the tray/menu state. The renderer listener only updates local UI state, so an unexpected native stop leaves the tray stuck in Recording/Paused until some later explicit set-recording-state call. The same gap exists in electron/ipc/recording/windows.ts.

🤖 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 `@electron/ipc/recording/mac.ts` around lines 166 - 175, The broadcast to
renderer windows (BrowserWindow.getAllWindows().forEach(...) sending
"recording-state-changed") updates only UI but skips the tray/menu pipeline;
after sending the renderer event, also invoke the same internal callback used by
main (onRecordingStateChange) with the identical payload ({ recording: false,
paused: false, sourceName }) so the tray/menu state is refreshed; apply the same
change to the Windows implementation in electron/ipc/recording/windows.ts.
electron/ipc/register/recording.ts (1)

1813-1829: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't reinitialize cursor capture on pause/resume updates.

Once paused became part of this IPC contract, Line 1814 still treats every recording === true call as a fresh start. A pause or resume transition will now clear buffered cursor samples, reset the capture clock, and restart sampling, even though Lines 1858-1867 already model pause boundaries separately. Gate this setup/teardown work to real start/stop transitions only.

🤖 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 `@electron/ipc/register/recording.ts` around lines 1813 - 1829, Gate the heavy
cursor init/teardown to real recording start/stop transitions instead of any
call with recording === true; detect the previous recording state (e.g., read an
existing getter like getIsRecording() or introduce a module-scoped prevRecording
flag) and only run the startup sequence (stopCursorCapture,
stopInteractionCapture, startWindowBoundsCapture, startNativeCursorMonitor,
setIsCursorCaptureActive, setActiveCursorSamples, setPendingCursorSamples,
setCursorCaptureStartTimeMs, resetCursorCaptureClock, setLinuxCursorScreenPoint,
setLastLeftClick, sampleCursorPoint, startCursorSampling,
startInteractionCapture) when prevRecording === false && recording === true, and
only run the teardown when prevRecording === true && recording === false; leave
pause/resume updates (the logic already around lines 1858-1867) to handle paused
toggles without clearing buffers or resetting clocks.
🧹 Nitpick comments (2)
src/hooks/useScreenRecorder.ts (1)

1371-1375: Tray stop only handled via generic channel; legacy listener appears unused
In src/hooks/useScreenRecorder.ts, stopRecording.current() is triggered by the generic onTrayRecordingCommand when command === "stop" (~2227-2240). The legacy onStopRecordingFromTray subscription (~1369-1375) listens for "stop-recording-from-tray", but the repo contains no corresponding sender/emit path for that channel (only the preload listener).
Optional cleanup: remove or gate the legacy listener to prevent a future double-stop regression.

🤖 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/hooks/useScreenRecorder.ts` around lines 1371 - 1375, The legacy tray
handler onStopRecordingFromTray in useScreenRecorder registers a listener that
calls stopRecording.current(), but the code already handles tray "stop" via
onTrayRecordingCommand, so keep them from double-invoking stop. Remove the
onStopRecordingFromTray subscription or guard it so it only registers when
onTrayRecordingCommand is not available (check
window.electronAPI?.onTrayRecordingCommand first); update the cleanup assignment
to match the chosen approach and ensure stopRecording.current is only called
once. Reference the functions/fields: useScreenRecorder,
window.electronAPI.onStopRecordingFromTray,
window.electronAPI.onTrayRecordingCommand, and stopRecording.current when making
the change.
src/components/launch/popovers/MorePopover.test.tsx (1)

168-174: ⚡ Quick win

Add coverage for the countdown-disabled start state.

countdownActive now gates the primary launch action, but this suite only exercises the enabled idle path. A small assertion here would lock down the new branch and catch regressions in the menu state logic.

Suggested test
 it("shows the start recording shortcut in idle state", () => {
 	const buttons = renderButtons();
 	const startButton = findButton(buttons, "Start Recording");

 	expect(startButton).toBeDefined();
 	expect(extractText(startButton)).toContain("Ctrl + Shift + R");
 });
+
+it("disables start while the countdown is active", () => {
+	const buttons = renderButtons({ countdownActive: true });
+	const startButton = findButton(buttons, "Start Recording");
+
+	expect(startButton).toBeDefined();
+	expect(startButton?.props.disabled).toBe(true);
+});
🤖 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/components/launch/popovers/MorePopover.test.tsx` around lines 168 - 174,
Update the test to also cover the countdown-disabled path: call renderButtons
with countdownActive: true (or otherwise simulate the countdownActive state)
then locate the "Start Recording" action with findButton and assert that this
primary launch action is disabled (e.g.,
expect(startButton).toHaveAttribute('aria-disabled', 'true') or
expect(startButton).toBeDisabled()) to lock down the branch introduced by
countdownActive; use the existing renderButtons, findButton and extractText
helpers to implement this assertion.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@electron/ipc/register/settings.ts`:
- Around line 223-245: The loop over config entries calls
toElectronAccelerator(binding) without validating that binding is an object with
the expected shape, so a malformed persisted entry can throw and abort
registering all shortcuts; before calling toElectronAccelerator (inside the
for...of that uses isLaunchShortcutAction), check that binding is an object and
binding.key is a non-empty string (and any other expected fields),
log/console.warn and continue for invalid entries, and/or wrap the per-binding
conversion/registration (the call to toElectronAccelerator and
globalShortcut.register) in its own try/catch so one bad entry doesn't prevent
others from registering.

In `@electron/main.ts`:
- Around line 246-271: The tray commands must always go to the HUD overlay
window rather than falling back to mainWindow; update sendTrayRecordingCommand
to stop using mainWindow as a fallback and instead ensure a HUD overlay is
created (call createWindow() or the HUD-specific creator) and re-query
getHudOverlayWindow()—only send "tray-recording-command" when the resolved
target is the HUD overlay (and is not destroyed); if the HUD still doesn't exist
after creation, return without sending. Also remove reliance on mainWindow (and
be mindful of createEditorWindowWrapper re-binding mainWindow) so the command is
never routed to the editor.

In `@src/App.tsx`:
- Around line 62-65: The app currently mounts ShortcutsProvider around
LaunchWindow (wrapping <LaunchWindow /> and <Toaster />) which duplicates the
provider already mounted by the editor path and causes racey app-wide shortcut
registration; remove the ShortcutsProvider wrapper here so only the
editor-mounted provider owns global shortcuts (i.e., render <LaunchWindow /> and
<Toaster /> directly), or alternatively hoist a single ShortcutsProvider to the
true application root so ShortcutsProvider is instantiated exactly once; refer
to the ShortcutsProvider and LaunchWindow components to locate and change the
wrapper.

In `@src/components/launch/hooks/useLaunchShortcuts.ts`:
- Around line 84-111: On macOS the DOM keydown handler and the Electron
global-shortcut subscription can both fire for the same keypress causing double
execution; fix this by tracking recent Electron-triggered launches and ignoring
the DOM handler when an Electron trigger was just received: add a ref like
lastElectronTriggerRef used in the effect that subscribes to
window.electronAPI.onLaunchShortcutTriggered to set
lastElectronTriggerRef.current = Date.now() when it calls
runLaunchShortcut(action), and in the onKeyDown handler (inside the useEffect
that iterates LAUNCH_SHORTCUT_ACTIONS and calls matchesShortcut with
launchShortcuts and isMac) early-return when isMac && Date.now() -
(lastElectronTriggerRef.current || 0) < 100 (or similar small debounce window)
before calling runLaunchShortcut; ensure the ref is declared in the hook scope
so both effects can access it.

In `@src/components/video-editor/ShortcutsConfigDialog.tsx`:
- Around line 177-183: In handleSave, call persistShortcuts(draft, launchDraft)
and await its resolution before calling setShortcuts and setLaunchShortcuts so
live state isn't mutated on a failed save; wrap the await in try/catch, on
success call setShortcuts(draft), setLaunchShortcuts(launchDraft),
toast.success(t("shortcutsConfig.saved")) and closeConfig(), and on error show
toast.error with the error message and do not update live bindings or close the
dialog; reference handleSave, persistShortcuts, setShortcuts,
setLaunchShortcuts, draft, launchDraft, toast, and closeConfig when making the
change.

In `@src/contexts/ShortcutsContext.tsx`:
- Around line 73-85: The current expression calls .then on a possibly undefined
return from
window.electronAPI?.registerLaunchGlobalShortcuts?.(launchShortcuts), which can
throw; change it to first capture the call into a variable and only attach .then
if it's defined (e.g., const promise =
window.electronAPI?.registerLaunchGlobalShortcuts?.(launchShortcuts); if
(promise) promise.then(...)), or alternatively await the call inside an async
function after checking window.electronAPI and registerLaunchGlobalShortcuts
exist; update the logic around launchShortcuts and the .then result handling
(result?.success / result.failedRegistrations) accordingly so .then is never
invoked on undefined.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 2227-2231: The tray "start" case currently calls toggleRecording()
which can stop an active recording; change the onTrayRecordingCommand handler in
useScreenRecorder.ts so that the "start" command calls the explicit
startRecording() method (and only starts when not already recording) and ensure
"stop" calls stopRecording() (or the explicit stop method) rather than
toggleRecording(); update the switch in the callback to use startRecording() for
"start" and stopRecording() for "stop" to enforce start-only/stop-only semantics
and avoid accidental toggles.

In `@src/i18n/locales/es/dialogs.json`:
- Around line 48-50: Update the Spanish translations for the shortcut dialog
keys localShortcuts, globalShortcuts, and globalDescription so they are in
Spanish instead of English; locate the JSON entries "localShortcuts",
"globalShortcuts", and "globalDescription" in the Spanish locales file and
replace their English strings with the correct Spanish translations (e.g.,
"Atajos locales", "Atajos globales" and an equivalent Spanish sentence for
"Available on macOS even when Recordly is in the background.").

In `@src/i18n/locales/es/launch.json`:
- Line 13: The Spanish label for the launch menu key "startRecording" is missing
an accent; update its value from "Iniciar grabacion" to "Iniciar grabación" and
also fix the other newly added Spanish label in this file (the added entry at
the file's line 29) to include the appropriate accents (e.g., change "grabacion"
to "grabación" in that string) so both entries match proper orthography.

In `@src/i18n/locales/fr/dialogs.json`:
- Around line 48-50: Translate the three English strings for the French locale
by replacing the values for the keys "localShortcuts", "globalShortcuts", and
"globalDescription" in the dialogs.json fragment with proper French translations
(e.g., "Raccourcis locaux", "Raccourcis globaux d'enregistrement", and
"Disponible sur macOS même lorsque Recordly est en arrière-plan.") so the
shortcuts dialog is fully localized.

In `@src/i18n/locales/fr/launch.json`:
- Line 13: Update the French labels to use correct accents: change the
"startRecording" value to "Démarrer l'enregistrement" and the corresponding
reactivation label (e.g., "reactivateRecording") to "Réactiver l'enregistrement"
so both strings use proper French orthography and capitalization.

In `@src/i18n/locales/it/dialogs.json`:
- Around line 48-50: The three i18n strings localShortcuts, globalShortcuts, and
globalDescription in the Italian locale are still in English; update their
values to Italian (e.g., localShortcuts -> "Scorciatoie locali", globalShortcuts
-> "Scorciatoie globali di registrazione", globalDescription -> "Disponibili su
macOS anche quando Recordly è in background.") so the shortcuts dialog is fully
translated for Italian users.

In `@src/i18n/locales/ko/dialogs.json`:
- Around line 48-50: Translate the three new keys "localShortcuts",
"globalShortcuts", and "globalDescription" in the Korean locale (dialogs.json)
into proper Korean strings; update the values for those keys so they are fully
localized (keep the keys unchanged), ensure valid JSON string syntax (escape
quotes if needed) and match the tone of existing translations in this file so
the shortcuts UI is consistently Korean.

In `@src/i18n/locales/nl/dialogs.json`:
- Around line 48-50: The three English-only keys "localShortcuts",
"globalShortcuts", and "globalDescription" in src/i18n/locales/nl/dialogs.json
must be translated to Dutch; update the values for these keys with proper Dutch
strings (e.g., "Lokale snelkoppelingen" for localShortcuts, "Globale
opname-snelkoppelingen" or similar for globalShortcuts, and a Dutch equivalent
for globalDescription such as "Beschikbaar op macOS, zelfs wanneer Recordly op
de achtergrond draait.") so the Dutch locale is consistent.

In `@src/i18n/locales/pt-BR/dialogs.json`:
- Around line 48-50: Translate the three English shortcut label entries in the
pt-BR locale by replacing the values for "localShortcuts", "globalShortcuts",
and "globalDescription" in dialogs.json with Brazilian Portuguese equivalents
(e.g., "Atalhos locais", "Atalhos globais de gravação", and a Portuguese version
of the description such as "Disponível no macOS mesmo quando o Recordly está em
segundo plano."). Ensure the keys remain unchanged and only the string values
are updated so the UI shows consistent Portuguese text.

In `@src/i18n/locales/pt-BR/launch.json`:
- Line 13: Update the pt-BR translation value for the key "startRecording" in
the launch.json locale file: replace the current string "Iniciar gravacao" with
the correctly accented Portuguese "Iniciar gravação" so the startRecording entry
uses the proper spelling.

In `@src/i18n/locales/ru/dialogs.json`:
- Around line 48-50: Translate the three English strings in the Russian locale
by replacing the values for the JSON keys "localShortcuts", "globalShortcuts",
and "globalDescription" with their Russian equivalents so the shortcuts dialog
is fully localized; update "localShortcuts" to a Russian phrase for "Local
shortcuts", "globalShortcuts" to Russian for "Global recording shortcuts", and
"globalDescription" to Russian describing that these are available on macOS even
when the app is in the background.

In `@src/i18n/locales/zh-CN/dialogs.json`:
- Around line 48-50: The three English strings for the shortcut dialog (keys
localShortcuts, globalShortcuts, globalDescription) are untranslated; replace
their values with Simplified Chinese equivalents: set localShortcuts to "本地快捷键",
globalShortcuts to "全局录制快捷键", and globalDescription to "在 macOS 上可用,即使 Recordly
处于后台运行。".

In `@src/i18n/locales/zh-TW/dialogs.json`:
- Around line 48-50: Replace the English values for the keys localShortcuts,
globalShortcuts, and globalDescription in the zh-TW dialogs JSON with proper
Traditional Chinese translations: update "localShortcuts" to a Traditional
Chinese string for "Local shortcuts", "globalShortcuts" to one for "Global
recording shortcuts", and "globalDescription" to a Chinese sentence conveying
"Available on macOS even when Recordly is in the background."; keep the keys
unchanged (localShortcuts, globalShortcuts, globalDescription) and ensure the
JSON string quoting and punctuation remain valid.

---

Outside diff comments:
In `@electron/ipc/recording/mac.ts`:
- Around line 166-175: The broadcast to renderer windows
(BrowserWindow.getAllWindows().forEach(...) sending "recording-state-changed")
updates only UI but skips the tray/menu pipeline; after sending the renderer
event, also invoke the same internal callback used by main
(onRecordingStateChange) with the identical payload ({ recording: false, paused:
false, sourceName }) so the tray/menu state is refreshed; apply the same change
to the Windows implementation in electron/ipc/recording/windows.ts.

In `@electron/ipc/register/recording.ts`:
- Around line 1813-1829: Gate the heavy cursor init/teardown to real recording
start/stop transitions instead of any call with recording === true; detect the
previous recording state (e.g., read an existing getter like getIsRecording() or
introduce a module-scoped prevRecording flag) and only run the startup sequence
(stopCursorCapture, stopInteractionCapture, startWindowBoundsCapture,
startNativeCursorMonitor, setIsCursorCaptureActive, setActiveCursorSamples,
setPendingCursorSamples, setCursorCaptureStartTimeMs, resetCursorCaptureClock,
setLinuxCursorScreenPoint, setLastLeftClick, sampleCursorPoint,
startCursorSampling, startInteractionCapture) when prevRecording === false &&
recording === true, and only run the teardown when prevRecording === true &&
recording === false; leave pause/resume updates (the logic already around lines
1858-1867) to handle paused toggles without clearing buffers or resetting
clocks.

---

Nitpick comments:
In `@src/components/launch/popovers/MorePopover.test.tsx`:
- Around line 168-174: Update the test to also cover the countdown-disabled
path: call renderButtons with countdownActive: true (or otherwise simulate the
countdownActive state) then locate the "Start Recording" action with findButton
and assert that this primary launch action is disabled (e.g.,
expect(startButton).toHaveAttribute('aria-disabled', 'true') or
expect(startButton).toBeDisabled()) to lock down the branch introduced by
countdownActive; use the existing renderButtons, findButton and extractText
helpers to implement this assertion.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 1371-1375: The legacy tray handler onStopRecordingFromTray in
useScreenRecorder registers a listener that calls stopRecording.current(), but
the code already handles tray "stop" via onTrayRecordingCommand, so keep them
from double-invoking stop. Remove the onStopRecordingFromTray subscription or
guard it so it only registers when onTrayRecordingCommand is not available
(check window.electronAPI?.onTrayRecordingCommand first); update the cleanup
assignment to match the chosen approach and ensure stopRecording.current is only
called once. Reference the functions/fields: useScreenRecorder,
window.electronAPI.onStopRecordingFromTray,
window.electronAPI.onTrayRecordingCommand, and stopRecording.current when making
the change.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 65f14aff-1a86-4df7-bd16-9d2f412436dc

📥 Commits

Reviewing files that changed from the base of the PR and between 05fdfa4 and 3ac43c1.

📒 Files selected for processing (49)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/ipc/recording/mac.ts
  • electron/ipc/recording/windows.ts
  • electron/ipc/register/recording.ts
  • electron/ipc/register/settings.ts
  • electron/ipc/shortcutTypes.ts
  • electron/main.ts
  • electron/native/bin/darwin-arm64/recordly-native-cursor-monitor
  • electron/native/bin/darwin-arm64/recordly-system-cursors
  • electron/native/bin/darwin-arm64/recordly-window-list
  • electron/native/bin/darwin-x64/recordly-native-cursor-monitor
  • electron/native/bin/darwin-x64/recordly-system-cursors
  • electron/native/bin/darwin-x64/recordly-window-list
  • electron/preload.ts
  • src/App.tsx
  • src/components/launch/LaunchWindow.module.css
  • src/components/launch/LaunchWindow.tsx
  • src/components/launch/hooks/useLaunchShortcuts.ts
  • src/components/launch/hooks/useLaunchWindowActions.test.ts
  • src/components/launch/hooks/useLaunchWindowActions.ts
  • src/components/launch/hooks/useLaunchWindowSystemState.ts
  • src/components/launch/popovers/MorePopover.test.tsx
  • src/components/launch/popovers/MorePopover.tsx
  • src/components/launch/popovers/PopoverScaffold.tsx
  • src/components/video-editor/ShortcutsConfigDialog.tsx
  • src/contexts/ShortcutsContext.tsx
  • src/hooks/useScreenRecorder.ts
  • src/i18n/locales/en/dialogs.json
  • src/i18n/locales/en/launch.json
  • src/i18n/locales/es/dialogs.json
  • src/i18n/locales/es/launch.json
  • src/i18n/locales/fr/dialogs.json
  • src/i18n/locales/fr/launch.json
  • src/i18n/locales/it/dialogs.json
  • src/i18n/locales/it/launch.json
  • src/i18n/locales/ko/dialogs.json
  • src/i18n/locales/ko/launch.json
  • src/i18n/locales/nl/dialogs.json
  • src/i18n/locales/nl/launch.json
  • src/i18n/locales/pt-BR/dialogs.json
  • src/i18n/locales/pt-BR/launch.json
  • src/i18n/locales/ru/dialogs.json
  • src/i18n/locales/zh-CN/dialogs.json
  • src/i18n/locales/zh-CN/launch.json
  • src/i18n/locales/zh-TW/dialogs.json
  • src/i18n/locales/zh-TW/launch.json
  • src/lib/shortcuts.test.ts
  • src/lib/shortcuts.ts

Comment on lines +223 to +245
if (!config || typeof config !== "object") {
return { success: true };
}

const failedRegistrations: Array<{
action: LaunchShortcutAction;
accelerator: string;
}> = [];

for (const [action, binding] of Object.entries(
config as Record<string, ShortcutBinding>,
)) {
if (!isLaunchShortcutAction(action)) {
console.warn("Ignoring unknown launch shortcut action in config:", action);
continue;
}

const accelerator = toElectronAccelerator(binding);
if (!accelerator) {
continue;
}

const registered = globalShortcut.register(accelerator, () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate each shortcut binding before converting it.

Line 240 calls toElectronAccelerator(binding) without first proving that binding is an object with a string key. Because Lines 205-213 persist arbitrary JSON, one corrupt entry can throw on binding.key, hit the catch at Line 259, and prevent every remaining valid launch shortcut from registering.

Suggested hardening
 			for (const [action, binding] of Object.entries(
 				config as Record<string, ShortcutBinding>,
 			)) {
 				if (!isLaunchShortcutAction(action)) {
 					console.warn("Ignoring unknown launch shortcut action in config:", action);
 					continue;
 				}
+
+				if (
+					!binding ||
+					typeof binding !== "object" ||
+					typeof (binding as { key?: unknown }).key !== "string"
+				) {
+					console.warn("Ignoring malformed launch shortcut binding:", { action, binding });
+					continue;
+				}
 
 				const accelerator = toElectronAccelerator(binding);
 				if (!accelerator) {
 					continue;
 				}
🤖 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 `@electron/ipc/register/settings.ts` around lines 223 - 245, The loop over
config entries calls toElectronAccelerator(binding) without validating that
binding is an object with the expected shape, so a malformed persisted entry can
throw and abort registering all shortcuts; before calling toElectronAccelerator
(inside the for...of that uses isLaunchShortcutAction), check that binding is an
object and binding.key is a non-empty string (and any other expected fields),
log/console.warn and continue for invalid entries, and/or wrap the per-binding
conversion/registration (the call to toElectronAccelerator and
globalShortcut.register) in its own try/catch so one bad entry doesn't prevent
others from registering.

Comment thread electron/main.ts
Comment on lines +246 to +271
function sendTrayRecordingCommand(command: "start" | "pause" | "resume" | "stop") {
let targetWindow = getHudOverlayWindow() ?? mainWindow;
if (!targetWindow || targetWindow.isDestroyed()) {
createWindow();
targetWindow = getHudOverlayWindow() ?? mainWindow;
}

if (!targetWindow || targetWindow.isDestroyed()) {
return;
}

const sendCommand = () => {
if (!targetWindow || targetWindow.isDestroyed()) {
return;
}

targetWindow.webContents.send("tray-recording-command", command);
};

if (targetWindow.webContents.isLoadingMainFrame()) {
targetWindow.webContents.once("did-finish-load", sendCommand);
return;
}

sendCommand();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Route tray recording commands to the HUD only.

This helper falls back to mainWindow, but createEditorWindowWrapper() rebinds mainWindow to the editor at Lines 850-852. If the HUD overlay is absent, tray Start/Pause/Resume/Stop can be sent to the editor window instead of the launch window that owns these handlers, so the action is dropped.

Suggested fix
 function sendTrayRecordingCommand(command: "start" | "pause" | "resume" | "stop") {
-	let targetWindow = getHudOverlayWindow() ?? mainWindow;
+	let targetWindow = getHudOverlayWindow();
 	if (!targetWindow || targetWindow.isDestroyed()) {
 		createWindow();
-		targetWindow = getHudOverlayWindow() ?? mainWindow;
+		targetWindow = getHudOverlayWindow();
 	}
 
 	if (!targetWindow || targetWindow.isDestroyed()) {
 		return;
 	}
📝 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
function sendTrayRecordingCommand(command: "start" | "pause" | "resume" | "stop") {
let targetWindow = getHudOverlayWindow() ?? mainWindow;
if (!targetWindow || targetWindow.isDestroyed()) {
createWindow();
targetWindow = getHudOverlayWindow() ?? mainWindow;
}
if (!targetWindow || targetWindow.isDestroyed()) {
return;
}
const sendCommand = () => {
if (!targetWindow || targetWindow.isDestroyed()) {
return;
}
targetWindow.webContents.send("tray-recording-command", command);
};
if (targetWindow.webContents.isLoadingMainFrame()) {
targetWindow.webContents.once("did-finish-load", sendCommand);
return;
}
sendCommand();
}
function sendTrayRecordingCommand(command: "start" | "pause" | "resume" | "stop") {
let targetWindow = getHudOverlayWindow();
if (!targetWindow || targetWindow.isDestroyed()) {
createWindow();
targetWindow = getHudOverlayWindow();
}
if (!targetWindow || targetWindow.isDestroyed()) {
return;
}
const sendCommand = () => {
if (!targetWindow || targetWindow.isDestroyed()) {
return;
}
targetWindow.webContents.send("tray-recording-command", command);
};
if (targetWindow.webContents.isLoadingMainFrame()) {
targetWindow.webContents.once("did-finish-load", sendCommand);
return;
}
sendCommand();
}
🤖 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 `@electron/main.ts` around lines 246 - 271, The tray commands must always go to
the HUD overlay window rather than falling back to mainWindow; update
sendTrayRecordingCommand to stop using mainWindow as a fallback and instead
ensure a HUD overlay is created (call createWindow() or the HUD-specific
creator) and re-query getHudOverlayWindow()—only send "tray-recording-command"
when the resolved target is the HUD overlay (and is not destroyed); if the HUD
still doesn't exist after creation, return without sending. Also remove reliance
on mainWindow (and be mindful of createEditorWindowWrapper re-binding
mainWindow) so the command is never routed to the editor.

Comment thread src/App.tsx
Comment on lines +62 to +65
<ShortcutsProvider>
<LaunchWindow />
<Toaster className="pointer-events-auto" />
</>
</ShortcutsProvider>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid a second renderer owning app-wide shortcut registration.

ShortcutsProvider now registers/unregisters launch global shortcuts, and the editor path already mounts one provider. Adding another provider here means two windows can race on the same app-wide registration, so closing either window can unregister shortcuts for the other.

🤖 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 62 - 65, The app currently mounts ShortcutsProvider
around LaunchWindow (wrapping <LaunchWindow /> and <Toaster />) which duplicates
the provider already mounted by the editor path and causes racey app-wide
shortcut registration; remove the ShortcutsProvider wrapper here so only the
editor-mounted provider owns global shortcuts (i.e., render <LaunchWindow /> and
<Toaster /> directly), or alternatively hoist a single ShortcutsProvider to the
true application root so ShortcutsProvider is instantiated exactly once; refer
to the ShortcutsProvider and LaunchWindow components to locate and change the
wrapper.

Comment on lines +84 to +111
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.repeat) {
return;
}

for (const action of LAUNCH_SHORTCUT_ACTIONS) {
if (!matchesShortcut(event, launchShortcuts[action], isMac)) {
continue;
}

event.preventDefault();
event.stopPropagation();
runLaunchShortcut(action);
break;
}
};

window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [isMac, launchShortcuts, runLaunchShortcut]);

useEffect(() => {
const unsubscribe = window.electronAPI?.onLaunchShortcutTriggered?.((action) => {
runLaunchShortcut(action);
});
return () => unsubscribe?.();
}, [runLaunchShortcut]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid dispatching the same launch shortcut from both handlers on macOS.

These two effects can fire for the same key press when the launch window is focused: the DOM keydown path and the Electron global-shortcut callback. On macOS that turns toggle-style actions into double execution, so startRecording/stopRecording and muteMicrophone can cancel themselves out.

Suggested fix
 useEffect(() => {
+	if (isMac) {
+		return;
+	}
+
 	const onKeyDown = (event: KeyboardEvent) => {
 		if (event.repeat) {
 			return;
 		}
@@
 	window.addEventListener("keydown", onKeyDown);
 	return () => window.removeEventListener("keydown", onKeyDown);
 }, [isMac, launchShortcuts, runLaunchShortcut]);
📝 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
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.repeat) {
return;
}
for (const action of LAUNCH_SHORTCUT_ACTIONS) {
if (!matchesShortcut(event, launchShortcuts[action], isMac)) {
continue;
}
event.preventDefault();
event.stopPropagation();
runLaunchShortcut(action);
break;
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [isMac, launchShortcuts, runLaunchShortcut]);
useEffect(() => {
const unsubscribe = window.electronAPI?.onLaunchShortcutTriggered?.((action) => {
runLaunchShortcut(action);
});
return () => unsubscribe?.();
}, [runLaunchShortcut]);
useEffect(() => {
if (isMac) {
return;
}
const onKeyDown = (event: KeyboardEvent) => {
if (event.repeat) {
return;
}
for (const action of LAUNCH_SHORTCUT_ACTIONS) {
if (!matchesShortcut(event, launchShortcuts[action], isMac)) {
continue;
}
event.preventDefault();
event.stopPropagation();
runLaunchShortcut(action);
break;
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [isMac, launchShortcuts, runLaunchShortcut]);
useEffect(() => {
const unsubscribe = window.electronAPI?.onLaunchShortcutTriggered?.((action) => {
runLaunchShortcut(action);
});
return () => unsubscribe?.();
}, [runLaunchShortcut]);
🤖 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/components/launch/hooks/useLaunchShortcuts.ts` around lines 84 - 111, On
macOS the DOM keydown handler and the Electron global-shortcut subscription can
both fire for the same keypress causing double execution; fix this by tracking
recent Electron-triggered launches and ignoring the DOM handler when an Electron
trigger was just received: add a ref like lastElectronTriggerRef used in the
effect that subscribes to window.electronAPI.onLaunchShortcutTriggered to set
lastElectronTriggerRef.current = Date.now() when it calls
runLaunchShortcut(action), and in the onKeyDown handler (inside the useEffect
that iterates LAUNCH_SHORTCUT_ACTIONS and calls matchesShortcut with
launchShortcuts and isMac) early-return when isMac && Date.now() -
(lastElectronTriggerRef.current || 0) < 100 (or similar small debounce window)
before calling runLaunchShortcut; ensure the ref is declared in the hook scope
so both effects can access it.

Comment on lines 177 to +183
const handleSave = useCallback(async () => {
setShortcuts(draft);
await persistShortcuts(draft);
setLaunchShortcuts(launchDraft);
await persistShortcuts(draft, launchDraft);
toast.success(t("shortcutsConfig.saved"));
closeConfig();
}, [draft, setShortcuts, persistShortcuts, closeConfig, t]);
}, [draft, launchDraft, setShortcuts, setLaunchShortcuts, persistShortcuts, closeConfig, t]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist before mutating live shortcut state.

These setters run before persistShortcuts resolves. If the save fails, the dialog still updates live editor/global bindings, and ShortcutsContext can re-register unsaved launch shortcuts until restart.

Suggested fix
 	const handleSave = useCallback(async () => {
-		setShortcuts(draft);
-		setLaunchShortcuts(launchDraft);
-		await persistShortcuts(draft, launchDraft);
-		toast.success(t("shortcutsConfig.saved"));
-		closeConfig();
+		try {
+			await persistShortcuts(draft, launchDraft);
+			setShortcuts(draft);
+			setLaunchShortcuts(launchDraft);
+			toast.success(t("shortcutsConfig.saved"));
+			closeConfig();
+		} catch {
+			toast.error(t("shortcutsConfig.saveFailed", "Failed to save shortcuts"));
+		}
 	}, [draft, launchDraft, setShortcuts, setLaunchShortcuts, persistShortcuts, closeConfig, t]);
📝 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
const handleSave = useCallback(async () => {
setShortcuts(draft);
await persistShortcuts(draft);
setLaunchShortcuts(launchDraft);
await persistShortcuts(draft, launchDraft);
toast.success(t("shortcutsConfig.saved"));
closeConfig();
}, [draft, setShortcuts, persistShortcuts, closeConfig, t]);
}, [draft, launchDraft, setShortcuts, setLaunchShortcuts, persistShortcuts, closeConfig, t]);
const handleSave = useCallback(async () => {
try {
await persistShortcuts(draft, launchDraft);
setShortcuts(draft);
setLaunchShortcuts(launchDraft);
toast.success(t("shortcutsConfig.saved"));
closeConfig();
} catch {
toast.error(t("shortcutsConfig.saveFailed", "Failed to save shortcuts"));
}
}, [draft, launchDraft, setShortcuts, setLaunchShortcuts, persistShortcuts, closeConfig, t]);
🤖 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/components/video-editor/ShortcutsConfigDialog.tsx` around lines 177 -
183, In handleSave, call persistShortcuts(draft, launchDraft) and await its
resolution before calling setShortcuts and setLaunchShortcuts so live state
isn't mutated on a failed save; wrap the await in try/catch, on success call
setShortcuts(draft), setLaunchShortcuts(launchDraft),
toast.success(t("shortcutsConfig.saved")) and closeConfig(), and on error show
toast.error with the error message and do not update live bindings or close the
dialog; reference handleSave, persistShortcuts, setShortcuts,
setLaunchShortcuts, draft, launchDraft, toast, and closeConfig when making the
change.

Comment on lines +48 to +50
"localShortcuts": "Local shortcuts",
"globalShortcuts": "Global recording shortcuts",
"globalDescription": "Available on macOS even when Recordly is in the background.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the new pt-BR shortcut labels instead of shipping English fallback text.

These three entries are user-visible in the Brazilian Portuguese locale, but the new values are still English. That will leak mixed-language UI in the shortcuts dialog.

Suggested fix
-		"localShortcuts": "Local shortcuts",
-		"globalShortcuts": "Global recording shortcuts",
-		"globalDescription": "Available on macOS even when Recordly is in the background.",
+		"localShortcuts": "Atalhos locais",
+		"globalShortcuts": "Atalhos globais de gravação",
+		"globalDescription": "Disponíveis no macOS mesmo quando o Recordly estiver em segundo plano.",
📝 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
"localShortcuts": "Local shortcuts",
"globalShortcuts": "Global recording shortcuts",
"globalDescription": "Available on macOS even when Recordly is in the background.",
"localShortcuts": "Atalhos locais",
"globalShortcuts": "Atalhos globais de gravação",
"globalDescription": "Disponíveis no macOS mesmo quando o Recordly estiver em segundo plano.",
🤖 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/i18n/locales/pt-BR/dialogs.json` around lines 48 - 50, Translate the
three English shortcut label entries in the pt-BR locale by replacing the values
for "localShortcuts", "globalShortcuts", and "globalDescription" in dialogs.json
with Brazilian Portuguese equivalents (e.g., "Atalhos locais", "Atalhos globais
de gravação", and a Portuguese version of the description such as "Disponível no
macOS mesmo quando o Recordly está em segundo plano."). Ensure the keys remain
unchanged and only the string values are updated so the UI shows consistent
Portuguese text.

"countdownDelay": "Atraso da contagem regressiva",
"noDelay": "Sem atraso",
"record": "Gravar",
"startRecording": "Iniciar gravacao",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the pt-BR spelling for startRecording.

"Iniciar gravacao" is missing the cedilla/tilde. In pt-BR this should be "Iniciar gravação".

🤖 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/i18n/locales/pt-BR/launch.json` at line 13, Update the pt-BR translation
value for the key "startRecording" in the launch.json locale file: replace the
current string "Iniciar gravacao" with the correctly accented Portuguese
"Iniciar gravação" so the startRecording entry uses the proper spelling.

Comment on lines +48 to +50
"localShortcuts": "Local shortcuts",
"globalShortcuts": "Global recording shortcuts",
"globalDescription": "Available on macOS even when Recordly is in the background.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the new shortcut strings in this locale.

These entries are still English, so the shortcuts dialog will render mixed-language copy for Russian users.

🤖 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/i18n/locales/ru/dialogs.json` around lines 48 - 50, Translate the three
English strings in the Russian locale by replacing the values for the JSON keys
"localShortcuts", "globalShortcuts", and "globalDescription" with their Russian
equivalents so the shortcuts dialog is fully localized; update "localShortcuts"
to a Russian phrase for "Local shortcuts", "globalShortcuts" to Russian for
"Global recording shortcuts", and "globalDescription" to Russian describing that
these are available on macOS even when the app is in the background.

Comment on lines +48 to +50
"localShortcuts": "Local shortcuts",
"globalShortcuts": "Global recording shortcuts",
"globalDescription": "Available on macOS even when Recordly is in the background.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the new shortcut strings in this locale.

These values are still English, so Simplified Chinese users will see a mixed-language shortcuts dialog.

🤖 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/i18n/locales/zh-CN/dialogs.json` around lines 48 - 50, The three English
strings for the shortcut dialog (keys localShortcuts, globalShortcuts,
globalDescription) are untranslated; replace their values with Simplified
Chinese equivalents: set localShortcuts to "本地快捷键", globalShortcuts to
"全局录制快捷键", and globalDescription to "在 macOS 上可用,即使 Recordly 处于后台运行。".

Comment on lines +48 to +50
"localShortcuts": "Local shortcuts",
"globalShortcuts": "Global recording shortcuts",
"globalDescription": "Available on macOS even when Recordly is in the background.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the new shortcut strings in this locale.

These values are still English, so Traditional Chinese users will see a mixed-language shortcuts dialog.

🤖 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/i18n/locales/zh-TW/dialogs.json` around lines 48 - 50, Replace the
English values for the keys localShortcuts, globalShortcuts, and
globalDescription in the zh-TW dialogs JSON with proper Traditional Chinese
translations: update "localShortcuts" to a Traditional Chinese string for "Local
shortcuts", "globalShortcuts" to one for "Global recording shortcuts", and
"globalDescription" to a Chinese sentence conveying "Available on macOS even
when Recordly is in the background."; keep the keys unchanged (localShortcuts,
globalShortcuts, globalDescription) and ensure the JSON string quoting and
punctuation remain valid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant