Skip to content

feat: settings tab with language picker, extension fixes, and i18n expansion#249

Merged
webadderall merged 9 commits intomainfrom
feat/settings-extensions
Apr 17, 2026
Merged

feat: settings tab with language picker, extension fixes, and i18n expansion#249
webadderall merged 9 commits intomainfrom
feat/settings-extensions

Conversation

@webadderall
Copy link
Copy Markdown
Collaborator

@webadderall webadderall commented Apr 16, 2026

Summary

New settings section in the editor sidebar, in-app language switching, extension install fixes, and expanded i18n.

Changes

  • Settings tab: New settings section in SettingsPanel — in-app language picker (all 5 locales), auto-apply fresh recording zooms toggle, connect-neighboring-zooms toggle, keybinds shortcut
  • VideoEditor: Wire autoApplyFreshRecordingAutoZooms preference through to SettingsPanel and zoom machinery
  • editorPreferences: Add autoApplyFreshRecordingAutoZooms field with tests
  • Extension install fix: Filter __MACOSX sidecar dirs when unzipping extensions uploaded from macOS Finder (fixes manifest-not-found on Finder zips)
  • useExtensions: Guard window.electronAPI access for SSR safety; reformat
  • I18nContext: Expose setLocale; persist locale preference
  • ShortcutsContext: Shortcut registration improvements
  • lib/extensions: renderHooks, extensionHost, fileUrls, cursorCoordinates updated for new extension capability surface
  • i18n: Expand extension locale strings across en, es, ko, nl, zh-CN

Summary by CodeRabbit

  • New Features

    • Settings panel: language selector, keyboard-shortcuts trigger, zoom mode (auto/manual), "auto-apply fresh recording zooms" and "connect neighboring zooms".
    • Clip controls: mute toggle, speed options, delete; timeline: collapsible timeline and inline toolbar (add layer/suggest zooms/split clip).
  • Improvements

    • Playback: skip/seek and mute/unmute controls; timeline time mapping for clip-aware playback.
    • UI icons refreshed and consolidated; expanded i18n strings (editor, timeline, extensions).

…pansion

- SettingsPanel: new 'settings' section — in-app language picker (all 5
  locales), auto-apply fresh recording zooms toggle, connect-neighboring-
  zooms toggle, keybinds shortcut into KeyboardShortcutsDialog
- VideoEditor: wire autoApplyFreshRecordingAutoZooms preference through to
  SettingsPanel; connect zoom settings into new section
- editorPreferences: add autoApplyFreshRecordingAutoZooms field; update tests
- extensionMarketplace: filter __MACOSX sidecar dirs when unzipping extensions
  uploaded from macOS Finder (fixes manifest-not-found on Finder zips)
- useExtensions: guard window.electronAPI for SSR safety; reformat to tabs
- I18nContext: expose setLocale; persist locale preference
- ShortcutsContext: shortcut registration improvements
- lib/extensions: renderHooks, extensionHost, fileUrls, cursorCoordinates
  updated for new extension capability surface
- i18n: expand extension locale strings across all 5 supported languages
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "*" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds timeline↔source time mapping and timeline imperative APIs; extends SettingsPanel with clip mute/speed and zoom auto-apply prefs; persists new editor preference; updates types/persistence for clip muted and track indices; swaps icon library and expands i18n strings.

Changes

Cohort / File(s) Summary
Settings & Editor UI
src/components/video-editor/SettingsPanel.tsx, src/components/video-editor/VideoEditor.tsx, src/components/video-editor/TutorialHelp.tsx, src/components/video-editor/ExtensionManager.tsx, src/components/video-editor/index.ts
Refactored SettingsPanel into sectioned renders (settings/zoom/clip), added clip mute/speed and auto-apply fresh-recording auto-zoom props/state, removed default export for SettingsPanel, swapped lucide→@phosphor icons, moved keyboard-shortcuts trigger to props, and adjusted header/toolbar rendering.
Timeline & Playback Mapping
src/components/video-editor/timeline/TimelineEditor.tsx, src/components/video-editor/VideoEditor.tsx
Converted TimelineEditor to forwardRef exposing TimelineEditorHandle, added playheadTime and playback props, introduced timeline↔source time mapping for playback/seek, remapped zoom regions through timeline mapping, and added timeline-invokable actions (addZoom/suggest/split/addAnnotation/addAudio).
Types & Persistence
src/components/video-editor/types.ts, src/components/video-editor/projectPersistence.ts
Added muted?: boolean to ClipRegion and trackIndex?: number to Audio/Annotation regions, added exported getClipSourceEndMs(), normalized clip muted and trackIndex in persistence, and constrained zoom mode normalization.
Preferences & Tests
src/components/video-editor/editorPreferences.ts, src/components/video-editor/editorPreferences.test.ts
Added autoApplyFreshRecordingAutoZooms preference (default true) with normalization; tests updated to include new preference and frame expectations.
I18n / Locale Files
src/contexts/I18nContext.tsx, src/i18n/config.ts, src/i18n/locales/*/{settings,extensions,editor,timeline,launch}.json
Adjusted locale bundle imports, added/updated translation keys (zoom modes/descriptions, clip labels, toolbar/timeline actions, audio label, toast.enableFailed, floating webcam preview strings).
Extensions & Hooks Robustness
src/hooks/useExtensions.ts, src/contexts/ShortcutsContext.tsx, src/lib/extensions/*
Made electronAPI access defensive for non-browser contexts, added try/catch/finally around discovery/activation, ensured canvas ctx.save()/restore() around extension hooks, and small API/formatting tweaks.
Misc / Packaging / Formatting
package.json, src/lib/extensions/*, src/lib/extensions/index.ts
Added @phosphor-icons/react dependency; multiple formatting, import reorder, newline fixes and re-export order tweaks without API-signature changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant VideoEditor
    participant TimelineEditor
    participant SettingsPanel
    participant ExportPipeline

    rect rgba(100,150,200,0.5)
    note over User,VideoEditor: Clip edit flows (mute/speed/split)
    User->>VideoEditor: request clip speed change / split / mute
    VideoEditor->>VideoEditor: compute getClipSourceEndMs and remap zoom regions
    VideoEditor->>TimelineEditor: update zoomRegions & playheadTime
    TimelineEditor->>TimelineEditor: render remapped timeline
    VideoEditor->>SettingsPanel: update clip controls (mute/speed state)
    end

    rect rgba(150,100,200,0.5)
    note over TimelineEditor,ExportPipeline: Timeline ↔ source mapping for seek/export
    User->>TimelineEditor: seek timeline (timelineTime)
    TimelineEditor->>VideoEditor: onSeek(timelineTime)
    VideoEditor->>VideoEditor: mapTimelineTimeToSourceTime(timelineTime)
    VideoEditor->>ExportPipeline: provide source time + effectiveZoomRegions
    ExportPipeline->>ExportPipeline: apply clip speeds and zooms for export
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • webadderall/Recordly#124 — Overlaps SettingsPanel/TimelineEditor/types/prefs edits; likely overlapping changes.
  • webadderall/Recordly#143 — Related SettingsPanel rendering/zoom-region UI refactor.
  • webadderall/Recordly#122 — Touches timeline, zoom/clip APIs and preference/type wiring; high potential for conflicts.

Suggested labels

Checked

Poem

🐰 I hopped through timelines with a tiny drum,
Clips can whisper, speed and zoom become,
Panels rearranged and icons swapped anew,
Languages and mappings stitched into view,
A cheerful rabbit stamps: the editor grew. 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.45% 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 accurately describes the main feature additions: a settings tab with language picker, extension-related fixes, and i18n expansion.
Description check ✅ Passed The description covers the key changes well, including settings tab, preferences, extension fixes, i18n context, and locale expansion, matching the template's required sections.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/settings-extensions

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

Caution

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

⚠️ Outside diff range comments (5)
src/lib/extensions/extensionHost.ts (1)

297-305: ⚠️ Potential issue | 🟠 Major

Always restore the canvas state when a hook throws.

If hook.hook(context) throws, Line 300 is skipped and the saved transform/clip leaks into later hooks. That makes one bad extension corrupt the rest of the render pipeline.

Proposed fix
 		for (const hook of hooks) {
-			try {
-				context.ctx.save();
+			context.ctx.save();
+			try {
 				hook.hook(context);
-				context.ctx.restore();
 			} catch (err) {
 				console.warn(
 					`[extensions] Render hook error (${hook.extensionId}, ${phase}):`,
 					err,
 				);
+			} finally {
+				context.ctx.restore();
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/extensions/extensionHost.ts` around lines 297 - 305, The render hook
wrapper currently calls context.ctx.save(), then hook.hook(context), and
context.ctx.restore() inside a try block so if hook.hook(context) throws the
restore is skipped; change the control flow in the render loop around the
save/hook/restore (the code that calls context.ctx.save(), hook.hook(context),
context.ctx.restore()) to ensure context.ctx.restore() is always executed by
moving the restore into a finally block (or equivalent) so every invocation of
hook.hook(context) is paired with a restore even on exceptions.
src/components/video-editor/VideoEditor.tsx (3)

1776-1815: ⚠️ Potential issue | 🟠 Major

The new auto-zoom preference is never saved.

This effect reruns when autoApplyFreshRecordingAutoZooms changes, but the object passed to saveEditorPreferences() omits that field. The toggle will revert on restart.

Proposed fix
 		saveEditorPreferences({
 			wallpaper,
 			shadowIntensity,
 			backgroundBlur,
 			zoomMotionBlur,
+			autoApplyFreshRecordingAutoZooms,
 			connectZooms,
 			zoomInDurationMs,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 1776 - 1815, The
saveEditorPreferences call is missing the auto-zoom preference so
autoApplyFreshRecordingAutoZooms never persists; update the object passed to
saveEditorPreferences inside the effect to include
autoApplyFreshRecordingAutoZooms (use the same property name as the state/prop)
so the preference is saved, referencing the saveEditorPreferences invocation and
the autoApplyFreshRecordingAutoZooms variable in VideoEditor.tsx.

1724-1773: ⚠️ Potential issue | 🟠 Major

Toggling this preference reloads the active editor state.

Because autoApplyFreshRecordingAutoZooms is in this effect's dependency list, flipping the switch reruns loadInitialData(), which can reopen the current project/session and reset in-memory edits. Keep the initial load effect stable and update pendingFreshRecordingAutoZoomPathRef in a separate small effect instead.

Minimal fix for the immediate reload issue
 	loadInitialData();
 }, [
 	applyLoadedProject,
-	autoApplyFreshRecordingAutoZooms,
 	smokeExportConfig.enabled,
 	smokeExportConfig.inputPath,
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 1724 - 1773, The
effect that runs loadInitialData should not depend on
autoApplyFreshRecordingAutoZooms because toggling it causes a full reload;
remove autoApplyFreshRecordingAutoZooms from the dependency array of the
useEffect that defines loadInitialData (the effect referencing loadInitialData
and setLoading). Instead add a small separate effect that watches
autoApplyFreshRecordingAutoZooms (and the current video path state, e.g.,
videoPath or videoSourcePath) and updates
pendingFreshRecordingAutoZoomPathRef.current accordingly: set it to videoPath
when autoApplyFreshRecordingAutoZooms is true, otherwise null. This preserves
loadInitialData stability while keeping pendingFreshRecordingAutoZoomPathRef in
sync.

5123-5126: ⚠️ Potential issue | 🟠 Major

Feed timeline-space zooms into TimelineEditor.

effectiveZoomRegions are already remapped into source time for playback/export. Passing them into the timeline editor makes the UI render/edit zooms in the wrong coordinate space once clip speed remapping is active.

Proposed fix
-						zoomRegions={effectiveZoomRegions}
+						zoomRegions={zoomRegions}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 5123 - 5126, The
TimelineEditor is receiving effectiveZoomRegions (which are remapped into source
time) so the timeline UI shows/edit zooms in the wrong coordinate space when
clip speed remapping is active; in the VideoEditor where TimelineEditor is
rendered (the zoomRegions prop currently set to effectiveZoomRegions), pass
timeline-space zooms instead — either the original timeline zoomRegions variable
(pre-remap) or run effectiveZoomRegions through the inverse remap function
(e.g., remapSourceToTimelineTime / remapZoomsToTimelineSpace) before passing to
TimelineEditor (update the prop usage where autoSuggestZoomsTrigger/onZoomAdded
are wired).
src/components/video-editor/projectPersistence.ts (1)

149-154: ⚠️ Potential issue | 🟠 Major

Don't flip legacy projects to the modern exporter by default yet.

Projects that never persisted exportPipelineModel will now silently switch export behavior on load, while ExportSettingsMenu still defaults to "legacy" in src/components/video-editor/ExportSettingsMenu.tsx:45-65. Please align every implicit default first, or add an explicit migration instead of changing the normalizer fallback in place.

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

In `@src/components/video-editor/projectPersistence.ts` around lines 149 - 154,
The current normalizeExportPipelineModel function flips unknown/absent values to
"modern", which will silently change legacy projects; change this to preserve
the existing implicit default by returning "legacy" for missing/unknown values
(or alternatively implement an explicit migration step where you detect absent
exportPipelineModel on project load and set it intentionally), and make sure
this behavior aligns with ExportSettingsMenu's default ("legacy") so that
normalizeExportPipelineModel (in projectPersistence.ts) and ExportSettingsMenu
(in ExportSettingsMenu.tsx) are consistent.
🧹 Nitpick comments (1)
src/i18n/locales/en/extensions.json (1)

2-59: Inconsistent indentation style with other locale files.

This file uses 8-space indentation while other locale files (ko, nl, zh-CN, es) use tab-based indentation. For consistency across the codebase, consider reformatting to match the tab-based style used in other locale files.

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

In `@src/i18n/locales/en/extensions.json` around lines 2 - 59, The JSON locale
uses 8-space indentation which is inconsistent with other locale files; reformat
the file to use tab-based indentation to match the style of existing locales and
preserve all keys such as "title", "tabs", "actions", "status", "detail",
"empty", "search", and "toast" (ensure values like "submit", "browse",
"installedAndEnabled", etc. remain unchanged while switching all leading
indentation to tabs).
🤖 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/video-editor/projectPersistence.ts`:
- Around line 365-384: The normalization in normalizedClipRegions (processing
editor.clipRegions) drops the clip's muted state by rebuilding each ClipRegion
without a muted field; update the mapping inside normalizedClipRegions (the .map
callback) to preserve the region.muted property when present (e.g., set muted:
Boolean(region.muted) or region.muted ?? false) so saved projects retain clip
mute state; ensure the returned object for each region includes the muted flag
alongside id, startMs, endMs, and speed.

In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 2187-2218: These new controls in SettingsPanel.tsx render
hardcoded English strings (e.g., the labels "Auto-apply fresh recording zooms"
and "Connect neighboring zooms" shown alongside the Switches bound to
autoApplyFreshRecordingAutoZooms/onAutoApplyFreshRecordingAutoZoomsChange and
connectZooms/onConnectZoomsChange); replace each hardcoded copy with the i18n
translation function (e.g., t('settings.autoApplyFreshRecordingZooms') and
t('settings.connectNeighboringZooms')) and wire the same keys into the locale
files so they translate after setLocale(...) — do the same for the other
affected blocks mentioned (lines ~2242-2290 and ~2376-2445) by converting all
literal strings like "Manual", "Mute Audio", "Delete Clip", etc., into t('...')
keys and adding those keys to the translation JSONs.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 2655-2665: When splitting a clip in VideoEditor.tsx the newly
created ClipRegion objects (left and right) copy target.speed but omit
target.muted, which causes muted clips to become unmuted after split; update the
left and right ClipRegion creation to include muted: target.muted so both halves
preserve the original muted state (refer to variables left, right, ClipRegion,
target, splitMs, leftId, rightId).
- Around line 3576-3582: The computed backendPreference currently falls back to
"auto" whenever smokeExportConfig.enabled is false, causing the
exportBackendPreference state/menu selection to be ignored; update the
backendPreference calculation (the expression that assigns backendPreference
using pipelineModel and smokeExportConfig) to use the exportBackendPreference
state value when not in smoke-export mode, preserving the legacy => "webcodecs"
mapping for pipelineModel === "legacy" and keeping the smoke-export logic intact
(i.e., when smokeExportConfig.enabled use smokeExportConfig.backendPreference or
smokeExportConfig.useNativeExport), so ModernVideoExporter receives the actual
selected exportBackendPreference instead of always "auto".

In `@src/contexts/ShortcutsContext.tsx`:
- Around line 41-48: The optional chain ends before .then causing a runtime
error when window.electronAPI is undefined; update the mount logic in
ShortcutsContext to first check or await the IPC method instead of chaining
.then on a possibly undefined value: e.g., test for
window.electronAPI?.getShortcuts before calling it (or convert the logic to an
async function and use try/catch around await
window.electronAPI.getShortcuts()), then call
setShortcuts(mergeWithDefaults(saved as Partial<ShortcutsConfig>)) only when
saved is truthy and log or ignore errors safely; target the code around the call
to window.electronAPI?.getShortcuts, setShortcuts, and mergeWithDefaults to
implement this guard/async-await+try/catch pattern.

In `@src/hooks/useExtensions.ts`:
- Around line 146-149: The catch block in the toggleExtension flow (in
useExtensions.ts, the async toggleExtension function that uses
activatingRef.current) should not rethrow the error because callers (e.g.,
ExtensionManager.tsx calling toggleExtension(detailData.ext.manifest.id)) don’t
await/catch it; remove the "throw err" and instead swallow the rejection by
logging the error and returning a resolved value (e.g., return false or the
previous state) so the promise never rejects unexpectedly; ensure the finally
block still runs to call activatingRef.current.delete(id).
- Around line 73-79: discoverAndSync currently returns early when
electronAPI?.extensionsDiscover is missing and can also exit on rejection from
electronAPI.extensionsDiscover() without calling setReady(true), leaving
consumers stuck; modify discoverAndSync so setReady(true) is always executed on
every exit path (for example, call setReady(true) before the early return when
extensions are unavailable and wrap the await
electronAPI.extensionsDiscover()/extensionHost.syncConfiguredExtensions calls in
try/catch/finally, invoking setReady(true) in the finally block), and ensure you
still return an empty array on the unsupported path or rethrow errors as
appropriate.

---

Outside diff comments:
In `@src/components/video-editor/projectPersistence.ts`:
- Around line 149-154: The current normalizeExportPipelineModel function flips
unknown/absent values to "modern", which will silently change legacy projects;
change this to preserve the existing implicit default by returning "legacy" for
missing/unknown values (or alternatively implement an explicit migration step
where you detect absent exportPipelineModel on project load and set it
intentionally), and make sure this behavior aligns with ExportSettingsMenu's
default ("legacy") so that normalizeExportPipelineModel (in
projectPersistence.ts) and ExportSettingsMenu (in ExportSettingsMenu.tsx) are
consistent.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 1776-1815: The saveEditorPreferences call is missing the auto-zoom
preference so autoApplyFreshRecordingAutoZooms never persists; update the object
passed to saveEditorPreferences inside the effect to include
autoApplyFreshRecordingAutoZooms (use the same property name as the state/prop)
so the preference is saved, referencing the saveEditorPreferences invocation and
the autoApplyFreshRecordingAutoZooms variable in VideoEditor.tsx.
- Around line 1724-1773: The effect that runs loadInitialData should not depend
on autoApplyFreshRecordingAutoZooms because toggling it causes a full reload;
remove autoApplyFreshRecordingAutoZooms from the dependency array of the
useEffect that defines loadInitialData (the effect referencing loadInitialData
and setLoading). Instead add a small separate effect that watches
autoApplyFreshRecordingAutoZooms (and the current video path state, e.g.,
videoPath or videoSourcePath) and updates
pendingFreshRecordingAutoZoomPathRef.current accordingly: set it to videoPath
when autoApplyFreshRecordingAutoZooms is true, otherwise null. This preserves
loadInitialData stability while keeping pendingFreshRecordingAutoZoomPathRef in
sync.
- Around line 5123-5126: The TimelineEditor is receiving effectiveZoomRegions
(which are remapped into source time) so the timeline UI shows/edit zooms in the
wrong coordinate space when clip speed remapping is active; in the VideoEditor
where TimelineEditor is rendered (the zoomRegions prop currently set to
effectiveZoomRegions), pass timeline-space zooms instead — either the original
timeline zoomRegions variable (pre-remap) or run effectiveZoomRegions through
the inverse remap function (e.g., remapSourceToTimelineTime /
remapZoomsToTimelineSpace) before passing to TimelineEditor (update the prop
usage where autoSuggestZoomsTrigger/onZoomAdded are wired).

In `@src/lib/extensions/extensionHost.ts`:
- Around line 297-305: The render hook wrapper currently calls
context.ctx.save(), then hook.hook(context), and context.ctx.restore() inside a
try block so if hook.hook(context) throws the restore is skipped; change the
control flow in the render loop around the save/hook/restore (the code that
calls context.ctx.save(), hook.hook(context), context.ctx.restore()) to ensure
context.ctx.restore() is always executed by moving the restore into a finally
block (or equivalent) so every invocation of hook.hook(context) is paired with a
restore even on exceptions.

---

Nitpick comments:
In `@src/i18n/locales/en/extensions.json`:
- Around line 2-59: The JSON locale uses 8-space indentation which is
inconsistent with other locale files; reformat the file to use tab-based
indentation to match the style of existing locales and preserve all keys such as
"title", "tabs", "actions", "status", "detail", "empty", "search", and "toast"
(ensure values like "submit", "browse", "installedAndEnabled", etc. remain
unchanged while switching all leading indentation to tabs).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7c8f3841-474b-4feb-a929-0123670284f8

📥 Commits

Reviewing files that changed from the base of the PR and between 0eee643 and 48cdb5c.

📒 Files selected for processing (20)
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/editorPreferences.test.ts
  • src/components/video-editor/editorPreferences.ts
  • src/components/video-editor/projectPersistence.ts
  • src/contexts/I18nContext.tsx
  • src/contexts/ShortcutsContext.tsx
  • src/hooks/useExtensions.ts
  • src/i18n/config.ts
  • src/i18n/locales/en/extensions.json
  • src/i18n/locales/es/extensions.json
  • src/i18n/locales/ko/extensions.json
  • src/i18n/locales/nl/extensions.json
  • src/i18n/locales/zh-CN/extensions.json
  • src/lib/extensions/cursorCoordinates.ts
  • src/lib/extensions/extensionHost.ts
  • src/lib/extensions/fileUrls.ts
  • src/lib/extensions/index.ts
  • src/lib/extensions/renderHooks.ts
  • src/lib/extensions/types.ts

Comment thread src/components/video-editor/projectPersistence.ts
Comment thread src/components/video-editor/SettingsPanel.tsx
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Comment thread src/contexts/ShortcutsContext.tsx Outdated
Comment thread src/hooks/useExtensions.ts Outdated
Comment thread src/hooks/useExtensions.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: 7

Caution

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

⚠️ Outside diff range comments (2)
src/components/video-editor/timeline/TimelineEditor.tsx (1)

644-700: ⚠️ Potential issue | 🟠 Major

Keep annotation cycling on the new playhead time source.

currentTimeMs now uses playheadTime ?? currentTime, but the Tab handler below still derives overlap from raw currentTime. When those diverge, cycling selects annotations for the wrong moment.

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

In `@src/components/video-editor/timeline/TimelineEditor.tsx` around lines 644 -
700, The Tab key annotation-cycling logic is still computing overlaps from the
raw currentTime while the rest of the component uses currentTimeMs (which
incorporates playheadTime); update the Tab handler in TimelineEditor to use
currentTimeMs (or Math.round((playheadTime ?? currentTime) * 1000) where
currentTimeMs is referenced) when deriving overlap/selection for annotations so
cycling selects based on the playhead-aware time source; keep the same units
(ms) and fallback behavior.
src/components/video-editor/SettingsPanel.tsx (1)

1375-1390: ⚠️ Potential issue | 🟡 Minor

Localize the new video-background toasts.

These messages bypass i18n, so this flow stays English even after switching the app language.

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

In `@src/components/video-editor/SettingsPanel.tsx` around lines 1375 - 1390, The
toasts in handleVideoUpload are hard-coded English; update handleVideoUpload to
use the app i18n strings (e.g., via the translation function used elsewhere such
as t or useTranslation) instead of literal text for toast.success and
toast.error messages and the "Unsupported format" description; locate
handleVideoUpload and replace the plain strings passed to
toast.error/toast.success with translated keys (e.g., t('video.added'),
t('video.import_failed'), t('video.unsupported_format') or whatever key names
your app uses) so the messages follow the app's localization pattern while
keeping the same toast calls and surrounding logic (references:
handleVideoUpload, isVideoWallpaperSource, setCustomImages, onWallpaperChange,
toast.success, toast.error).
🤖 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/video-editor/projectPersistence.ts`:
- Around line 305-339: The normalization currently rebuilds each ZoomRegion and
drops the mode field, causing manual-focus zooms to revert; update the mapper
that produces normalizedZoomRegions to copy/validate region.mode into the
returned object (e.g., preserve region.mode when it's a recognized value,
otherwise fall back to the default mode), alongside existing fields like id,
startMs, endMs, depth and focus; reference the normalizedZoomRegions mapping and
DEFAULT_ZOOM_DEPTH to locate where to add the mode preservation and validation
logic.

In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 2229-2237: The keybind strings are being translated with
tSettings("keyboardShortcuts.*") but those keys live in the editor namespace,
causing fallbacks to English; in SettingsPanel (where SectionLabel and
KeyboardShortcutsDialog are used) replace calls to tSettings for the
keyboardShortcuts keys with the editor namespace translator (e.g., tEditor or
the same translator used elsewhere in this file for editor strings) so the
KeyboardShortcutsDialog triggerLabel and any related labels pull from the editor
namespace translations instead of settings.json.

In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 1371-1379: The public imperative handle created in
useImperativeHandle exposes toggleCollapsed but it's a no-op and causes
lint/type failures; either implement it to actually toggle the editor collapse
state (hook into the existing collapse state and call the state setter or
collapse handler inside toggleCollapsed) or remove toggleCollapsed from the
exported TimelineEditorHandle type and from the object returned by
useImperativeHandle (update the useImperativeHandle call and the
TimelineEditorHandle definition so they stay consistent). Ensure references to
toggleCollapsed are updated elsewhere if you choose removal and keep other
handles (addZoom, suggestZooms, splitClip, addAnnotation, addAudio, keyframes)
unchanged.
- Around line 690-691: The prop default for onAspectRatioChange in
TimelineEditor (currently "onAspectRatioChange = () => {}") should be removed to
avoid an empty block; instead leave it undefined in the props signature (or
replace with a shared noop constant if your codebase uses one) and update call
sites to use optional chaining (e.g., onAspectRatioChange?.(...)) so missing
wiring isn't masked; modify the props destructuring where onAspectRatioChange is
declared and any places that call onAspectRatioChange to use the safe optional
call.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 4920-5079: The toolbar contains hardcoded user-facing strings
(e.g., the "Add Layer" label in the DropdownMenuTrigger, the DropdownMenuItem
texts "Annotation" and "Audio", button titles like "Add Zoom (Z)", "Suggest
Zooms from Cursor", "Split Clip (C)", playback/control titles "Skip Back",
"Play"/"Pause", "Skip Forward", the timeline collapse titles, and the volume
button title "Mute/Unmute") that bypass localization; replace each literal with
the project's localization calls (e.g., useTranslation().t or i18n.t depending
on your codebase) inside the VideoEditor component so visible labels and title
props call the translation function (e.g., t('videoEditor.addLayer'),
t('videoEditor.annotation'), t('videoEditor.addZoom'), etc.), and ensure you
import/use the same i18n hook already used elsewhere in this file and update the
keys consistently.
- Around line 1774-1777: The ref pendingFreshRecordingAutoZoomPathRef is being
set whenever autoApplyFreshRecordingAutoZooms and videoPath are truthy, which
re-arms fresh-recording auto-zoom for non-fresh videos; change the effect to
also require the "fresh" indicator before setting the ref (e.g., include the
boolean that denotes a fresh recording—such as isFreshRecording or similar in
your component state/props) so only when autoApplyFreshRecordingAutoZooms &&
videoPath && isFreshRecording do you set
pendingFreshRecordingAutoZoomPathRef.current = videoPath, otherwise set it to
null; update the dependency array to include that freshness flag.
- Around line 2718-2725: Validate the incoming speed parameter before doing
duration math: ensure speed is finite and greater than zero (e.g.,
Number.isFinite(speed) && speed > 0), and if invalid, bail out early (return) or
clamp to a safe minimum so you never divide by zero or a non-finite value when
computing sourceDurationMs, newEndMs, and scaleFactor; apply this check at the
start of the arrow handler that references selectedClipId, clipRegions,
clip.speed, oldSpeed, sourceDurationMs, newEndMs, and scaleFactor.

---

Outside diff comments:
In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 1375-1390: The toasts in handleVideoUpload are hard-coded English;
update handleVideoUpload to use the app i18n strings (e.g., via the translation
function used elsewhere such as t or useTranslation) instead of literal text for
toast.success and toast.error messages and the "Unsupported format" description;
locate handleVideoUpload and replace the plain strings passed to
toast.error/toast.success with translated keys (e.g., t('video.added'),
t('video.import_failed'), t('video.unsupported_format') or whatever key names
your app uses) so the messages follow the app's localization pattern while
keeping the same toast calls and surrounding logic (references:
handleVideoUpload, isVideoWallpaperSource, setCustomImages, onWallpaperChange,
toast.success, toast.error).

In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 644-700: The Tab key annotation-cycling logic is still computing
overlaps from the raw currentTime while the rest of the component uses
currentTimeMs (which incorporates playheadTime); update the Tab handler in
TimelineEditor to use currentTimeMs (or Math.round((playheadTime ?? currentTime)
* 1000) where currentTimeMs is referenced) when deriving overlap/selection for
annotations so cycling selects based on the playhead-aware time source; keep the
same units (ms) and fallback behavior.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f41a62b6-88b8-41c2-a10f-785afb056ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 48cdb5c and 993c7af.

📒 Files selected for processing (15)
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/TutorialHelp.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/projectPersistence.ts
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/types.ts
  • src/contexts/ShortcutsContext.tsx
  • src/hooks/useExtensions.ts
  • src/i18n/locales/en/extensions.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/es/settings.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/nl/settings.json
  • src/i18n/locales/zh-CN/settings.json
  • src/lib/extensions/extensionHost.ts
✅ Files skipped from review due to trivial changes (5)
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/es/settings.json
  • src/contexts/ShortcutsContext.tsx
  • src/i18n/locales/zh-CN/settings.json
  • src/lib/extensions/extensionHost.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/i18n/locales/en/extensions.json
  • src/hooks/useExtensions.ts

Comment thread src/components/video-editor/projectPersistence.ts
Comment thread src/components/video-editor/SettingsPanel.tsx
Comment thread src/components/video-editor/timeline/TimelineEditor.tsx Outdated
Comment thread src/components/video-editor/timeline/TimelineEditor.tsx Outdated
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Comment thread src/components/video-editor/VideoEditor.tsx
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
src/components/video-editor/VideoEditor.tsx (1)

1218-1317: ⚠️ Potential issue | 🟠 Major

Add frame to this memo's dependency list.

frame is part of the memoized persisted state, but it is missing from the dependency array. If the user changes only the frame, Line 1559 keeps using stale project data until some unrelated state also changes.

Proposed fix
 		[
 			buildPersistedEditorState,
 			wallpaper,
@@
 			borderRadius,
 			padding,
+			frame,
 			webcam,
 			zoomRegions,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 1218 - 1317,
currentPersistedEditorState calls buildPersistedEditorState with frame included
in the state object but frame is missing from the useMemo dependency array; add
frame to the dependency array for the useMemo that defines
currentPersistedEditorState so updates to frame will recompute the memoized
value (look for the useMemo that declares currentPersistedEditorState and ensure
"frame" is present in its dependency list).
src/components/video-editor/timeline/TimelineEditor.tsx (1)

1312-1316: ⚠️ Potential issue | 🟠 Major

Use the timeline playhead for annotation cycling.

Line 1313 still derives overlap from currentTime instead of the new timeline-aware playhead. In sped-up / remapped clips, Tab will cycle annotations for the wrong moment even though add/seek/cursor logic already uses playheadTime.

Proposed fix
-        const currentTimeMs = Math.round(currentTime * 1000);
+        const currentTimeMs = Math.round((playheadTime ?? currentTime) * 1000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/timeline/TimelineEditor.tsx` around lines 1312 -
1316, The Tab key handler uses currentTime to find overlapping annotations, but
the timeline-aware playhead value playheadTime should be used instead; in the
block inside the e.key === 'Tab' check (where annotationRegions is
filtered/sorted) replace uses of currentTime/currentTimeMs with playheadTime
(compute const currentTimeMs = Math.round(playheadTime * 1000)) so cycling uses
the timeline playhead consistent with add/seek/cursor logic.
♻️ Duplicate comments (2)
src/components/video-editor/VideoEditor.tsx (2)

5011-5016: ⚠️ Potential issue | 🟡 Minor

Localize the play/pause tooltip too.

Line 5016 still hardcodes English even though this PR adds localized playback labels, so language switching leaves this control partially untranslated.

Proposed fix
-										title={isPlaying ? "Pause" : "Play"}
+										title={
+											isPlaying
+												? t("editor.playback.pause")
+												: t("editor.playback.play")
+										}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 5011 - 5016, The
Button's title prop is hardcoded to English; replace title={isPlaying ? "Pause"
: "Play"} with the component's existing localized playback labels so the tooltip
follows language switching (e.g. title={isPlaying ? playbackLabels.pause :
playbackLabels.play} or title={t(isPlaying ? 'pause' : 'play')} depending on the
localization helper used). Update the usage near the Button with
variant/size/togglePlayPause/isPlaying to reference those localized keys and
ensure any needed import or hook (playbackLabels or t) already present in the
component is used.

1742-1751: ⚠️ Potential issue | 🟠 Major

Don't arm fresh-recording auto-zoom from the generic video path.

Lines 1750-1751 still set pendingFreshRecordingAutoZoomPathRef for any getCurrentVideoPath() result, so a normal loaded video can be treated like a fresh recording and trigger auto-suggest.

Proposed fix
-					pendingFreshRecordingAutoZoomPathRef.current =
-						autoApplyFreshRecordingAutoZooms ? sourceVideoUrl : null;
+					pendingFreshRecordingAutoZoomPathRef.current = null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 1742 - 1751, The
code currently arms pendingFreshRecordingAutoZoomPathRef for any path returned
by window.electronAPI.getCurrentVideoPath(), which treats loaded videos like
fresh recordings; only set pendingFreshRecordingAutoZoomPathRef.current when the
returned result explicitly represents a fresh recording (e.g., check a flag such
as result.isFreshRecording or result.isTemporaryRecording) AND
autoApplyFreshRecordingAutoZooms is true. Update the getCurrentVideoPath
call/contract (or detect via a temp/fresh-recording path pattern) and change the
block around setVideoSourcePath/setVideoPath to conditionally set
pendingFreshRecordingAutoZoomPathRef.current only when that fresh-recording
indicator is present; reference symbols: window.electronAPI.getCurrentVideoPath,
pendingFreshRecordingAutoZoomPathRef, autoApplyFreshRecordingAutoZooms,
setVideoSourcePath, setVideoPath.
🤖 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/video-editor/timeline/TimelineEditor.tsx`:
- Around line 88-92: TimelineEditor is not reading the passed hideToolbar prop,
so wire it through: add hideToolbar to the TimelineEditor props/interface
(matches the snippet prop name), destructure it in the TimelineEditor component,
and use it to conditionally skip rendering the built-in toolbar (or pass it down
to the internal toolbar component, e.g., TimelineToolbar) so the parent
VideoEditor’s toolbar isn’t duplicated; ensure any child components that render
the toolbar (lines referenced ~643-694) also accept and honor the hideToolbar
boolean.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 2655-2677: handleClipSplit currently splits a ClipRegion but
leaves selectedClipId pointing at the removed region; update the selection
inside handleClipSplit so the UI doesn't remain stale. After creating
leftId/rightId (and before returning the new array) check if selectedClipId ===
target.id (use the component state setter setSelectedClipId) and either
setSelectedClipId(leftId) (or rightId) or clear it (null) based on desired UX.
Modify the useCallback for handleClipSplit to import/close over selectedClipId
and call setSelectedClipId when the target is split so selection is remapped
from the removed region to a valid new ClipRegion id.

---

Outside diff comments:
In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 1312-1316: The Tab key handler uses currentTime to find
overlapping annotations, but the timeline-aware playhead value playheadTime
should be used instead; in the block inside the e.key === 'Tab' check (where
annotationRegions is filtered/sorted) replace uses of currentTime/currentTimeMs
with playheadTime (compute const currentTimeMs = Math.round(playheadTime *
1000)) so cycling uses the timeline playhead consistent with add/seek/cursor
logic.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 1218-1317: currentPersistedEditorState calls
buildPersistedEditorState with frame included in the state object but frame is
missing from the useMemo dependency array; add frame to the dependency array for
the useMemo that defines currentPersistedEditorState so updates to frame will
recompute the memoized value (look for the useMemo that declares
currentPersistedEditorState and ensure "frame" is present in its dependency
list).

---

Duplicate comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 5011-5016: The Button's title prop is hardcoded to English;
replace title={isPlaying ? "Pause" : "Play"} with the component's existing
localized playback labels so the tooltip follows language switching (e.g.
title={isPlaying ? playbackLabels.pause : playbackLabels.play} or
title={t(isPlaying ? 'pause' : 'play')} depending on the localization helper
used). Update the usage near the Button with
variant/size/togglePlayPause/isPlaying to reference those localized keys and
ensure any needed import or hook (playbackLabels or t) already present in the
component is used.
- Around line 1742-1751: The code currently arms
pendingFreshRecordingAutoZoomPathRef for any path returned by
window.electronAPI.getCurrentVideoPath(), which treats loaded videos like fresh
recordings; only set pendingFreshRecordingAutoZoomPathRef.current when the
returned result explicitly represents a fresh recording (e.g., check a flag such
as result.isFreshRecording or result.isTemporaryRecording) AND
autoApplyFreshRecordingAutoZooms is true. Update the getCurrentVideoPath
call/contract (or detect via a temp/fresh-recording path pattern) and change the
block around setVideoSourcePath/setVideoPath to conditionally set
pendingFreshRecordingAutoZoomPathRef.current only when that fresh-recording
indicator is present; reference symbols: window.electronAPI.getCurrentVideoPath,
pendingFreshRecordingAutoZoomPathRef, autoApplyFreshRecordingAutoZooms,
setVideoSourcePath, setVideoPath.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7664cdb7-082a-457d-a03d-cfe1f2c30557

📥 Commits

Reviewing files that changed from the base of the PR and between 993c7af and 0206e38.

📒 Files selected for processing (14)
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/projectPersistence.ts
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/i18n/locales/en/editor.json
  • src/i18n/locales/en/timeline.json
  • src/i18n/locales/es/editor.json
  • src/i18n/locales/es/timeline.json
  • src/i18n/locales/ko/editor.json
  • src/i18n/locales/ko/timeline.json
  • src/i18n/locales/nl/editor.json
  • src/i18n/locales/nl/timeline.json
  • src/i18n/locales/zh-CN/editor.json
  • src/i18n/locales/zh-CN/timeline.json
✅ Files skipped from review due to trivial changes (9)
  • src/i18n/locales/en/timeline.json
  • src/i18n/locales/es/timeline.json
  • src/i18n/locales/ko/timeline.json
  • src/i18n/locales/nl/timeline.json
  • src/i18n/locales/zh-CN/timeline.json
  • src/i18n/locales/en/editor.json
  • src/i18n/locales/es/editor.json
  • src/i18n/locales/zh-CN/editor.json
  • src/components/video-editor/SettingsPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/video-editor/projectPersistence.ts

Comment thread src/components/video-editor/timeline/TimelineEditor.tsx Outdated
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (2)
src/components/video-editor/timeline/TimelineEditor.tsx (1)

122-126: ⚠️ Potential issue | 🟠 Major

Honor hideToolbar before rendering the built-in toolbar.

hideToolbar is part of the public props, but it is never destructured or checked before Line 1779. VideoEditor can pass the flag and still get this toolbar rendered, which leaves the parent and child toolbars duplicated.

Proposed fix
 export interface TimelineEditorProps {
@@
 	onVolumeChange?: (volume: number) => void;
 	hideToolbar?: boolean;
 }
@@
 		{
@@
 			isCropped = false,
 			videoPath,
+			hideToolbar = false,
 		},
 		ref,
 	) {
@@
-		return (
-			<div className="flex-1 min-h-0 flex flex-col bg-[`#17171a`] overflow-auto">
-				<div className="flex items-center gap-2 px-4 py-2 border-b border-white/10 bg-[`#161619`]">
+		return (
+			<div className="flex-1 min-h-0 flex flex-col bg-[`#17171a`] overflow-auto">
+				{!hideToolbar ? (
+				<div className="flex items-center gap-2 px-4 py-2 border-b border-white/10 bg-[`#161619`]">
 					...
-				</div>
+				</div>
+				) : null}
 				<div

Also applies to: 697-750, 1777-1779

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

In `@src/components/video-editor/timeline/TimelineEditor.tsx` around lines 122 -
126, The TimelineEditor component is not honoring the public prop hideToolbar so
the built-in toolbar still renders even when VideoEditor passes hideToolbar;
destructure hideToolbar from the component props (alongside isPlaying,
onTogglePlayPause, volume, onVolumeChange) and guard the toolbar render paths by
checking if (!hideToolbar) before rendering the built-in toolbar UI (the toolbar
render block(s) used internally by TimelineEditor/its renderToolbar helper);
apply this check to each toolbar rendering location referenced (the
header/parent toolbar and the embedded child toolbar render sections) so the
toolbar is skipped when hideToolbar is true.
src/components/video-editor/VideoEditor.tsx (1)

5047-5048: ⚠️ Potential issue | 🟡 Minor

Play/Pause button title bypasses localization.

The title attribute uses hardcoded English strings while surrounding buttons use t() for localization.

Proposed fix
-title={isPlaying ? "Pause" : "Play"}
+title={isPlaying ? t("editor.playback.pause", "Pause") : t("editor.playback.play", "Play")}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 5047 - 5048, The
Play/Pause button's title prop currently uses hardcoded English strings
("Pause"/"Play") which bypasses localization; update the title to use the i18n
function (t) instead, e.g., call t('pause') / t('play') based on the isPlaying
boolean. Locate the button rendering in VideoEditor.tsx where title={isPlaying ?
"Pause" : "Play"} and replace the string literals with the appropriate t(...)
keys (ensure the keys match existing translations or add them to the locale
files) so the button title is localized.
🧹 Nitpick comments (1)
src/components/video-editor/VideoEditor.tsx (1)

658-663: Consider moving pure helper outside component.

formatTime is a pure function with no dependencies on component state. Moving it outside the component avoids recreating the function reference on every render.

+function formatTime(seconds: number) {
+  if (!isFinite(seconds) || isNaN(seconds) || seconds < 0) return "0:00";
+  const mins = Math.floor(seconds / 60);
+  const secs = Math.floor(seconds % 60);
+  return `${mins}:${secs.toString().padStart(2, "0")}`;
+}
+
 export default function VideoEditor() {
   const { t } = useI18n();
-  function formatTime(seconds: number) {
-    if (!isFinite(seconds) || isNaN(seconds) || seconds < 0) return "0:00";
-    const mins = Math.floor(seconds / 60);
-    const secs = Math.floor(seconds % 60);
-    return `${mins}:${secs.toString().padStart(2, "0")}`;
-  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 658 - 663,
formatTime is a pure helper currently defined inside the component causing a new
function to be created on every render; move the formatTime function to
module/top-level scope (outside the VideoEditor component) so it’s declared
once, keep the same signature formatTime(seconds: number), and export it if it’s
used elsewhere; update usages in the component to call the top-level formatTime
and remove any lingering closure references.
🤖 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/video-editor/ExtensionManager.tsx`:
- Around line 97-107: The extension homepage link is rendered directly from
untrusted metadata; validate the URL scheme before rendering an anchor by
parsing extension.manifest.homepage with the URL constructor and only render a
clickable <a> when the parsed protocol is "http:" or "https:"; otherwise render
the homepage as plain text (or omit the link). Update the rendering logic in
ExtensionManager (the block that reads extension.manifest.homepage and the
similar blocks at the other occurrences) to perform this check and keep the
onClick={(e) => e.stopPropagation()} behavior only for safe links.
- Around line 709-713: The useEffect in ExtensionManager.tsx keeps re-triggering
handleSearch when activeTab === "browse" and marketplaceResults.length === 0
because marketplaceLoading flips to false; add a useRef (e.g.,
marketplaceAutoSearchedRef) to track whether the auto-search for the browse tab
has already run, set it true when calling handleSearch, and include it in the
effect check so handleSearch only runs once per tab entry (reset the ref when
leaving the browse tab or when a manual search is initiated); update the effect
condition to check !marketplaceAutoSearchedRef.current along with activeTab,
marketplaceResults.length === 0, and !marketplaceLoading, and set
marketplaceAutoSearchedRef.current = true immediately before invoking
handleSearch().
- Around line 721-726: When an install succeeds you update marketplace list but
not the modal state, so the detail modal still shows Install; after calling
setMarketplaceResults in the success branch (inside the install handler) also
update the open modal state by setting detailData.ext.installed to true (use the
existing state updater, e.g. setDetailData or the detail state setter used in
ExtensionManager) so the detail modal reflects the installed status; locate the
success block that checks result.success and add a call to update detailData.ext
(or replace detailData with a new object cloning ext and setting installed:
true) to keep the Install button in sync with the marketplaceResults update.

In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 2225-2231: sceneSectionContent currently renders
renderExtensionPanelsForSections("zoom", ...) causing zoom-related extension
panels to appear under Scene but disappear when the user opens the dedicated
Zoom section; update the switch handling for case "zoom" (and the
zoomItemSectionContent) to also include renderExtensionPanelsForSections("zoom",
"appearance", "zoom", "frame", "crop") (or move the call out of
sceneSectionContent into the Zoom branch) so any extension panels with
parentSection: "zoom" are rendered inside the Zoom section; apply the same
change to the other occurrences noted (around the other ranges mentioned) to
ensure zoom extension panels are consistently rendered when the Zoom page is
opened.
- Around line 204-220: The numeric readout currently falls back to 0 instead of
the slider's default; update the badge rendering to mirror the input's value
logic by showing field.defaultValue when value is not a number. Locate the
slider/value logic around value, field.defaultValue and the onChange that calls
extensionHost.setExtensionSetting(extensionId, field.id, ...) and change the
badge expression (the span that currently uses (typeof value === "number" ?
value : 0).toFixed(1)) to use (typeof value === "number" ? value :
(field.defaultValue as number)).toFixed(1) so the displayed number matches the
actual default used by the input.

In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 595-600: The timeline no longer renders or edits
trimRegions/speedRegions because rows for them were removed and
handleItemSpanChange doesn't route their drag/resize updates; reintroduce
filters and row creation for trim and speed just like zoom/clip/annotation/audio
(use trimRegions, speedRegions and the corresponding TRIM_ROW_ID and
SPEED_ROW_ID identifiers) so Timeline builds rows for them, and update
handleItemSpanChange to handle items whose rowId equals TRIM_ROW_ID or
SPEED_ROW_ID (route span changes into the existing trimRegions/speedRegions
normalization and selection/deletion flows) ensuring their drag/resize events
update state and persist via the same normalization logic already used for other
rows.
- Around line 1481-1505: The Tab cycling uses currentTime instead of the
remapped playhead time so overlapping detection is wrong when timeline time is
remapped; in TimelineEditor replace the currentTime usage when computing
currentTimeMs with the mapped time (use playheadTime ?? currentTime) so the
overlapping filter on annotationRegions and subsequent selection logic
(selectedAnnotationId, onSelectAnnotation) operates at the correct time; locate
the block handling the "Tab" key and update the computation of currentTimeMs
accordingly.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 5205-5208: The TimelineEditor usage in VideoEditor is passing
unused props isPlaying, onTogglePlayPause, volume, and onVolumeChange; remove
these four props from the TimelineEditor JSX at the call site (the lines passing
isPlaying={isPlaying}, onTogglePlayPause={togglePlayPause},
volume={previewVolume}, onVolumeChange={setPreviewVolume}) since TimelineEditor
does not destructure or use them and VideoEditor handles playback when
hideToolbar={true}; update the JSX to only pass needed props and run a quick
compile to ensure no references remain to those prop variables in this call
site.

---

Duplicate comments:
In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 122-126: The TimelineEditor component is not honoring the public
prop hideToolbar so the built-in toolbar still renders even when VideoEditor
passes hideToolbar; destructure hideToolbar from the component props (alongside
isPlaying, onTogglePlayPause, volume, onVolumeChange) and guard the toolbar
render paths by checking if (!hideToolbar) before rendering the built-in toolbar
UI (the toolbar render block(s) used internally by TimelineEditor/its
renderToolbar helper); apply this check to each toolbar rendering location
referenced (the header/parent toolbar and the embedded child toolbar render
sections) so the toolbar is skipped when hideToolbar is true.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 5047-5048: The Play/Pause button's title prop currently uses
hardcoded English strings ("Pause"/"Play") which bypasses localization; update
the title to use the i18n function (t) instead, e.g., call t('pause') /
t('play') based on the isPlaying boolean. Locate the button rendering in
VideoEditor.tsx where title={isPlaying ? "Pause" : "Play"} and replace the
string literals with the appropriate t(...) keys (ensure the keys match existing
translations or add them to the locale files) so the button title is localized.

---

Nitpick comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 658-663: formatTime is a pure helper currently defined inside the
component causing a new function to be created on every render; move the
formatTime function to module/top-level scope (outside the VideoEditor
component) so it’s declared once, keep the same signature formatTime(seconds:
number), and export it if it’s used elsewhere; update usages in the component to
call the top-level formatTime and remove any lingering closure references.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 533845b9-b902-493d-94dc-ded8720253de

📥 Commits

Reviewing files that changed from the base of the PR and between 0206e38 and c7ed811.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json
  • src/components/video-editor/ExtensionManager.tsx
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/index.ts
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/types.ts
  • src/i18n/locales/ko/launch.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/nl/launch.json
✅ Files skipped from review due to trivial changes (3)
  • package.json
  • src/i18n/locales/ko/launch.json
  • src/i18n/locales/nl/launch.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/i18n/locales/ko/settings.json
  • src/components/video-editor/types.ts

Comment thread src/components/video-editor/ExtensionManager.tsx
Comment thread src/components/video-editor/ExtensionManager.tsx
Comment thread src/components/video-editor/ExtensionManager.tsx
Comment thread src/components/video-editor/SettingsPanel.tsx Outdated
Comment thread src/components/video-editor/SettingsPanel.tsx
Comment thread src/components/video-editor/timeline/TimelineEditor.tsx
Comment thread src/components/video-editor/timeline/TimelineEditor.tsx
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/components/video-editor/SettingsPanel.tsx (1)

1364-1379: ⚠️ Potential issue | 🟡 Minor

Localize the new video-background toasts.

These new toast.*(...) messages are still hardcoded English, so this upload flow won't follow the locale selected from the new Settings page. Please route them through tSettings(...)/t(...) and add matching locale keys.

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

In `@src/components/video-editor/SettingsPanel.tsx` around lines 1364 - 1379, The
toast messages in handleVideoUpload are hardcoded English; replace each
toast.*(...) call in handleVideoUpload with localized strings using
tSettings(...) or t(...) (e.g.,
toast.error(tSettings("video_upload.unsupported_format"), { description:
tSettings("video_upload.select_video_description") }) and
toast.success(tSettings("video_upload.added"))), ensuring you import/usage of
tSettings where handleVideoUpload is defined and update the locale files with
keys like video_upload.unsupported_format,
video_upload.select_video_description, video_upload.added, and
video_upload.import_failed to match the error/success cases used around
isVideoWallpaperSource, setCustomImages, and onWallpaperChange.
♻️ Duplicate comments (1)
src/components/video-editor/VideoEditor.tsx (1)

1765-1775: ⚠️ Potential issue | 🟠 Major

Don't arm fresh-recording auto-zoom from the generic video-load fallback.

This getCurrentVideoPath() branch is not freshness-aware, so assigning pendingFreshRecordingAutoZoomPathRef here can re-trigger auto-suggest for ordinary project/video loads once there is no active recording session. Keep it null here unless you have an explicit fresh-recording signal.

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

In `@src/components/video-editor/VideoEditor.tsx` around lines 1765 - 1775, The
branch handling window.electronAPI.getCurrentVideoPath() is not freshness-aware
so must not arm pendingFreshRecordingAutoZoomPathRef from this generic load;
remove or change the assignment that sets
pendingFreshRecordingAutoZoomPathRef.current = autoApplyFreshRecordingAutoZooms
? sourceVideoUrl : null and instead always set
pendingFreshRecordingAutoZoomPathRef.current = null here (leave setting video
via setVideoSourcePath/setVideoPath and clearing project/snapshot as-is), so
only explicit fresh-recording code paths arm the ref; locate these symbols:
getCurrentVideoPath, pendingFreshRecordingAutoZoomPathRef,
autoApplyFreshRecordingAutoZooms, setVideoSourcePath, setVideoPath.
🤖 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/video-editor/VideoEditor.tsx`:
- Around line 2426-2435: The handler handleSelectZoom should also clear any
selected annotation so zoom controls become accessible; inside the truthy-id
branch (where setSelectedZoomId(id) and setActiveEffectSection("zoom") are
called) add setSelectedAnnotationId(null) alongside setSelectedTrimId(null) and
setSelectedAudioId(null) so AnnotationSettingsPanel won't render for a lingering
selectedAnnotationId.

---

Outside diff comments:
In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 1364-1379: The toast messages in handleVideoUpload are hardcoded
English; replace each toast.*(...) call in handleVideoUpload with localized
strings using tSettings(...) or t(...) (e.g.,
toast.error(tSettings("video_upload.unsupported_format"), { description:
tSettings("video_upload.select_video_description") }) and
toast.success(tSettings("video_upload.added"))), ensuring you import/usage of
tSettings where handleVideoUpload is defined and update the locale files with
keys like video_upload.unsupported_format,
video_upload.select_video_description, video_upload.added, and
video_upload.import_failed to match the error/success cases used around
isVideoWallpaperSource, setCustomImages, and onWallpaperChange.

---

Duplicate comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 1765-1775: The branch handling
window.electronAPI.getCurrentVideoPath() is not freshness-aware so must not arm
pendingFreshRecordingAutoZoomPathRef from this generic load; remove or change
the assignment that sets pendingFreshRecordingAutoZoomPathRef.current =
autoApplyFreshRecordingAutoZooms ? sourceVideoUrl : null and instead always set
pendingFreshRecordingAutoZoomPathRef.current = null here (leave setting video
via setVideoSourcePath/setVideoPath and clearing project/snapshot as-is), so
only explicit fresh-recording code paths arm the ref; locate these symbols:
getCurrentVideoPath, pendingFreshRecordingAutoZoomPathRef,
autoApplyFreshRecordingAutoZooms, setVideoSourcePath, setVideoPath.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5eadbab2-add6-4aff-b1f9-b99dd0194ad6

📥 Commits

Reviewing files that changed from the base of the PR and between c7ed811 and 6861a41.

📒 Files selected for processing (4)
  • src/components/video-editor/ExtensionManager.tsx
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/timeline/TimelineEditor.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/video-editor/ExtensionManager.tsx

Comment thread src/components/video-editor/VideoEditor.tsx
# Conflicts:
#	src/components/video-editor/ExtensionManager.tsx
#	src/components/video-editor/SettingsPanel.tsx
#	src/components/video-editor/TutorialHelp.tsx
#	src/components/video-editor/VideoEditor.tsx
#	src/components/video-editor/index.ts
#	src/components/video-editor/projectPersistence.ts
#	src/components/video-editor/timeline/TimelineEditor.tsx
#	src/components/video-editor/types.ts
#	src/i18n/locales/nl/launch.json
# Conflicts:
#	src/components/video-editor/timeline/TimelineEditor.tsx
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.

1 participant