diff --git a/src/components/video-editor/timeline/timelineLayout.test.ts b/src/components/video-editor/timeline/timelineLayout.test.ts
index 38c26138..42cdd329 100644
--- a/src/components/video-editor/timeline/timelineLayout.test.ts
+++ b/src/components/video-editor/timeline/timelineLayout.test.ts
@@ -2,10 +2,8 @@ import { describe, expect, it } from "vitest";
import {
getTimelineContentMinHeightPx,
getTimelineRowsMinHeightPx,
- getTimelineViewportStretchFactor,
TIMELINE_AXIS_HEIGHT_PX,
TIMELINE_ROW_MIN_HEIGHT_PX,
- TIMELINE_VISIBLE_ROW_COUNT,
} from "./timelineLayout";
describe("timelineLayout", () => {
@@ -30,12 +28,4 @@ describe("timelineLayout", () => {
TIMELINE_AXIS_HEIGHT_PX + 2 * TIMELINE_ROW_MIN_HEIGHT_PX,
);
});
-
- it("caps the default timeline viewport to two visible rows", () => {
- expect(TIMELINE_VISIBLE_ROW_COUNT).toBe(2);
- expect(getTimelineViewportStretchFactor(2)).toBe(1);
- expect(getTimelineViewportStretchFactor(4)).toBe(2);
- expect(getTimelineViewportStretchFactor(5)).toBe(2.5);
- expect(getTimelineViewportStretchFactor(0)).toBe(1);
- });
});
diff --git a/src/components/video-editor/timeline/timelineLayout.ts b/src/components/video-editor/timeline/timelineLayout.ts
index 0f54c4b4..bdf03a17 100644
--- a/src/components/video-editor/timeline/timelineLayout.ts
+++ b/src/components/video-editor/timeline/timelineLayout.ts
@@ -1,6 +1,5 @@
export const TIMELINE_AXIS_HEIGHT_PX = 32;
export const TIMELINE_ROW_MIN_HEIGHT_PX = 28;
-export const TIMELINE_VISIBLE_ROW_COUNT = 2;
function normalizeRowCount(rowCount: number) {
if (!Number.isFinite(rowCount)) {
@@ -17,13 +16,3 @@ export function getTimelineRowsMinHeightPx(rowCount: number) {
export function getTimelineContentMinHeightPx(rowCount: number) {
return TIMELINE_AXIS_HEIGHT_PX + getTimelineRowsMinHeightPx(rowCount);
}
-
-export function getTimelineViewportStretchFactor(rowCount: number) {
- const normalizedRowCount = normalizeRowCount(rowCount);
-
- if (normalizedRowCount <= 0) {
- return 1;
- }
-
- return Math.max(1, normalizedRowCount / TIMELINE_VISIBLE_ROW_COUNT);
-}
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json
index 162f2f14..c788b132 100644
--- a/src/i18n/locales/en/settings.json
+++ b/src/i18n/locales/en/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "Webcam footage linked",
"webcamFootageRemoved": "Webcam footage removed",
"webcamSize": "Webcam Size",
- "webcamMirror": "Mirror Webcam",
"webcamReactToZoom": "Webcam Reacts To Zoom",
"webcamRoundness": "Webcam Roundness",
"webcamShadow": "Webcam Shadow",
diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json
index 9d7617b7..ab686b30 100644
--- a/src/i18n/locales/es/settings.json
+++ b/src/i18n/locales/es/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "Metraje de cámara vinculado",
"webcamFootageRemoved": "Metraje de cámara eliminado",
"webcamSize": "Tamaño de cámara",
- "webcamMirror": "Reflejar cámara",
"webcamReactToZoom": "La cámara reacciona al zoom",
"webcamRoundness": "Redondez de cámara",
"webcamShadow": "Sombra de cámara",
diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json
index 0342c078..c4748e3d 100644
--- a/src/i18n/locales/fr/settings.json
+++ b/src/i18n/locales/fr/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "Vidéo de webcam liée",
"webcamFootageRemoved": "Vidéo de webcam supprimée",
"webcamSize": "Taille de la webcam",
- "webcamMirror": "Inverser la webcam",
"webcamReactToZoom": "La webcam réagit au zoom",
"webcamRoundness": "Arrondi de la webcam",
"webcamShadow": "Ombre de la webcam",
diff --git a/src/i18n/locales/ko/settings.json b/src/i18n/locales/ko/settings.json
index 592f176c..62f6f8fd 100644
--- a/src/i18n/locales/ko/settings.json
+++ b/src/i18n/locales/ko/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "웹캠 영상을 연결했습니다",
"webcamFootageRemoved": "웹캠 영상을 제거했습니다",
"webcamSize": "웹캠 크기",
- "webcamMirror": "웹캠 좌우 반전",
"webcamReactToZoom": "확대 시 웹캠 반응",
"webcamRoundness": "웹캠 둥글기",
"webcamShadow": "웹캠 그림자",
diff --git a/src/i18n/locales/nl/settings.json b/src/i18n/locales/nl/settings.json
index 259c1696..2aaa43e9 100644
--- a/src/i18n/locales/nl/settings.json
+++ b/src/i18n/locales/nl/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "Webcambeelden gekoppeld",
"webcamFootageRemoved": "Webcambeelden verwijderd",
"webcamSize": "Webcamgrootte",
- "webcamMirror": "Webcam spiegelen",
"webcamReactToZoom": "Webcam reageert op zoom",
"webcamRoundness": "Webcam-afronding",
"webcamShadow": "Webcamschaduw",
diff --git a/src/i18n/locales/pt-BR/settings.json b/src/i18n/locales/pt-BR/settings.json
index 0f9e834d..86e969d5 100644
--- a/src/i18n/locales/pt-BR/settings.json
+++ b/src/i18n/locales/pt-BR/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "Filmagem da webcam vinculada",
"webcamFootageRemoved": "Filmagem da webcam removida",
"webcamSize": "Tamanho da webcam",
- "webcamMirror": "Espelhar webcam",
"webcamReactToZoom": "Webcam reage ao zoom",
"webcamRoundness": "Arredondamento da webcam",
"webcamShadow": "Sombra da webcam",
diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json
index 508ec4d0..dae55a79 100644
--- a/src/i18n/locales/zh-CN/settings.json
+++ b/src/i18n/locales/zh-CN/settings.json
@@ -85,7 +85,6 @@
"webcamFootageAdded": "已关联摄像头素材",
"webcamFootageRemoved": "已移除摄像头素材",
"webcamSize": "摄像头大小",
- "webcamMirror": "镜像摄像头",
"webcamReactToZoom": "摄像头随缩放变化",
"webcamRoundness": "摄像头圆角",
"webcamShadow": "摄像头阴影",
From dc59b8dd0a21e53c2dc1f8f893ff994e1f32030a Mon Sep 17 00:00:00 2001
From: webadderall <131426131+webadderall@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:50:30 +1000
Subject: [PATCH 8/9] Loop wallpaper videos during export
---
src/lib/exporter/modernFrameRenderer.ts | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/src/lib/exporter/modernFrameRenderer.ts b/src/lib/exporter/modernFrameRenderer.ts
index c300a454..bb343c93 100644
--- a/src/lib/exporter/modernFrameRenderer.ts
+++ b/src/lib/exporter/modernFrameRenderer.ts
@@ -862,18 +862,6 @@ export class FrameRenderer {
videoSrc = await getAssetPath(wallpaper.replace(/^\//, ""));
}
- try {
- const frameSource = new ForwardFrameSource();
- await frameSource.initialize(videoSrc);
- this.backgroundForwardFrameSource = frameSource;
- return;
- } catch (error) {
- console.warn(
- "[FrameRenderer] Decoder-backed wallpaper source unavailable during export; falling back to media element sync:",
- error,
- );
- }
-
const backgroundSource = await resolveMediaElementSource(videoSrc);
this.cleanupBackgroundSource = backgroundSource.revoke;
From 798740cd19252fa1d78aed624c4da3d1faa80383 Mon Sep 17 00:00:00 2001
From: webadderall <131426131+webadderall@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:52:15 +1000
Subject: [PATCH 9/9] fix(timeline): restore visible track row height
---
.../video-editor/timeline/TimelineEditor.tsx | 7 +++++--
.../video-editor/timeline/TimelineWrapper.tsx | 2 +-
.../video-editor/timeline/timelineLayout.test.ts | 10 ++++++++++
.../video-editor/timeline/timelineLayout.ts | 11 +++++++++++
4 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx
index b2a97a1e..128ac5c9 100644
--- a/src/components/video-editor/timeline/TimelineEditor.tsx
+++ b/src/components/video-editor/timeline/TimelineEditor.tsx
@@ -63,6 +63,8 @@ import TimelineWrapper from "./TimelineWrapper";
import {
getTimelineContentMinHeightPx,
getTimelineRowsMinHeightPx,
+ getTimelineViewportStretchFactor,
+ TIMELINE_AXIS_HEIGHT_PX,
} from "./timelineLayout";
import { type AudioPeaksData, useAudioPeaks } from "./useAudioPeaks";
import { buildInteractionZoomSuggestions } from "./zoomSuggestionUtils";
@@ -709,13 +711,14 @@ function Timeline({
const timelineRowCount = 2 + annotationRowIds.length + audioRowIds.length;
const timelineRowsMinHeightPx = getTimelineRowsMinHeightPx(timelineRowCount);
const timelineContentMinHeightPx = getTimelineContentMinHeightPx(timelineRowCount);
+ const timelineViewportStretchFactor = getTimelineViewportStretchFactor(timelineRowCount);
return (
(
}
return (
-
+
{hideToolbar ? null : (
diff --git a/src/components/video-editor/timeline/TimelineWrapper.tsx b/src/components/video-editor/timeline/TimelineWrapper.tsx
index fed0431c..32a94565 100644
--- a/src/components/video-editor/timeline/TimelineWrapper.tsx
+++ b/src/components/video-editor/timeline/TimelineWrapper.tsx
@@ -375,7 +375,7 @@ export default function TimelineWrapper({
autoScroll={{ enabled: false }}
resizeHandleWidth={28}
>
-
+
{children}
{/* Floating tooltip shown during drag/resize */}
{
@@ -28,4 +30,12 @@ describe("timelineLayout", () => {
TIMELINE_AXIS_HEIGHT_PX + 2 * TIMELINE_ROW_MIN_HEIGHT_PX,
);
});
+
+ it("stretches content height to keep a two-row viewport", () => {
+ expect(TIMELINE_VISIBLE_ROW_COUNT).toBe(2);
+ expect(getTimelineViewportStretchFactor(2)).toBe(1);
+ expect(getTimelineViewportStretchFactor(4)).toBe(2);
+ expect(getTimelineViewportStretchFactor(5)).toBe(2.5);
+ expect(getTimelineViewportStretchFactor(0)).toBe(1);
+ });
});
diff --git a/src/components/video-editor/timeline/timelineLayout.ts b/src/components/video-editor/timeline/timelineLayout.ts
index bdf03a17..0f54c4b4 100644
--- a/src/components/video-editor/timeline/timelineLayout.ts
+++ b/src/components/video-editor/timeline/timelineLayout.ts
@@ -1,5 +1,6 @@
export const TIMELINE_AXIS_HEIGHT_PX = 32;
export const TIMELINE_ROW_MIN_HEIGHT_PX = 28;
+export const TIMELINE_VISIBLE_ROW_COUNT = 2;
function normalizeRowCount(rowCount: number) {
if (!Number.isFinite(rowCount)) {
@@ -16,3 +17,13 @@ export function getTimelineRowsMinHeightPx(rowCount: number) {
export function getTimelineContentMinHeightPx(rowCount: number) {
return TIMELINE_AXIS_HEIGHT_PX + getTimelineRowsMinHeightPx(rowCount);
}
+
+export function getTimelineViewportStretchFactor(rowCount: number) {
+ const normalizedRowCount = normalizeRowCount(rowCount);
+
+ if (normalizedRowCount <= 0) {
+ return 1;
+ }
+
+ return Math.max(1, normalizedRowCount / TIMELINE_VISIBLE_ROW_COUNT);
+}