From 5dc076cac42af3dbf81a2ce610f410817a2c199b Mon Sep 17 00:00:00 2001
From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com>
Date: Tue, 31 Mar 2026 19:33:11 +0100
Subject: [PATCH] feat(desktop): stop recording overlay and stable in-progress
bounds
---
.../routes/(window-chrome)/new-main/index.tsx | 36 ++++++++++++++
.../src/routes/in-progress-recording.tsx | 48 ++++++++++++++-----
2 files changed, 73 insertions(+), 11 deletions(-)
diff --git a/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx b/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
index 03d7cc274d..3543b1960e 100644
--- a/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
+++ b/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
@@ -979,6 +979,8 @@ function Page() {
const { rawOptions, setOptions } = useRecordingOptions();
const currentRecording = createCurrentRecordingQuery();
const isRecording = () => !!currentRecording.data;
+ const isActivelyRecording = () =>
+ currentRecording.data?.status === "recording";
const auth = authStore.createQuery();
const [hasHiddenMainWindowForPicker, setHasHiddenMainWindowForPicker] =
@@ -1508,6 +1510,20 @@ function Page() {
const license = createLicenseQuery();
const signIn = createSignInMutation();
+ const stopRecording = createMutation(() => ({
+ mutationFn: async () => {
+ try {
+ await commands.stopRecording();
+ } catch (error) {
+ await dialog.message(
+ `Failed to stop recording: ${
+ error instanceof Error ? error.message : String(error)
+ }`,
+ { title: "Stop Recording", kind: "error" },
+ );
+ }
+ },
+ }));
const BaseControls = () => (
@@ -1994,6 +2010,26 @@ function Page() {
+
+
+
+
+
+
+
);
diff --git a/apps/desktop/src/routes/in-progress-recording.tsx b/apps/desktop/src/routes/in-progress-recording.tsx
index f9b9df3e2f..d3718916c2 100644
--- a/apps/desktop/src/routes/in-progress-recording.tsx
+++ b/apps/desktop/src/routes/in-progress-recording.tsx
@@ -1,4 +1,3 @@
-import { createElementBounds } from "@solid-primitives/bounds";
import { createTimer } from "@solid-primitives/timer";
import { createMutation } from "@tanstack/solid-query";
import { LogicalPosition } from "@tauri-apps/api/dpi";
@@ -111,8 +110,8 @@ function InProgressRecordingInner() {
const [startingDismissed, setStartingDismissed] = createSignal(false);
const [interactiveAreaRef, setInteractiveAreaRef] =
createSignal(null);
- const interactiveBounds = createElementBounds(interactiveAreaRef);
let settingsButtonRef: HTMLButtonElement | undefined;
+ let lastInteractiveBoundsKey = "";
const recordingMode = createMemo(
() => currentRecording.data?.mode ?? optionsQuery.rawOptions.mode,
);
@@ -292,30 +291,55 @@ function InProgressRecordingInner() {
void refreshCameraWindowState();
});
- createEffect(() => {
+ const syncInteractiveAreaBounds = () => {
const element = interactiveAreaRef();
if (!element) {
- void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME);
+ if (lastInteractiveBoundsKey !== "") {
+ lastInteractiveBoundsKey = "";
+ void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME);
+ }
return;
}
- const left = interactiveBounds.left ?? 0;
- const top = interactiveBounds.top ?? 0;
- const width = interactiveBounds.width ?? 0;
- const height = interactiveBounds.height ?? 0;
+ const rect = element.getBoundingClientRect();
+ if (rect.width === 0 || rect.height === 0) return;
- if (width === 0 || height === 0) return;
+ const key = [rect.left, rect.top, rect.width, rect.height]
+ .map((value) => value.toFixed(2))
+ .join(":");
+ if (key === lastInteractiveBoundsKey) return;
+ lastInteractiveBoundsKey = key;
void commands.setFakeWindowBounds(FAKE_WINDOW_BOUNDS_NAME, {
- position: { x: left, y: top },
- size: { width, height },
+ position: { x: rect.left, y: rect.top },
+ size: { width: rect.width, height: rect.height },
});
+ };
+
+ createEffect(() => {
+ interactiveAreaRef();
+ queueMicrotask(syncInteractiveAreaBounds);
+ });
+
+ createEffect(() => {
+ state();
+ issuePanelVisible();
+ queueMicrotask(syncInteractiveAreaBounds);
});
onCleanup(() => {
+ lastInteractiveBoundsKey = "";
void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME);
});
+ onMount(() => {
+ const onResize = () => syncInteractiveAreaBounds();
+ window.addEventListener("resize", onResize);
+ onCleanup(() => window.removeEventListener("resize", onResize));
+ requestAnimationFrame(() => syncInteractiveAreaBounds());
+ setTimeout(() => syncInteractiveAreaBounds(), 150);
+ });
+
createTimer(
() => {
void refreshCameraWindowState();
@@ -324,6 +348,8 @@ function InProgressRecordingInner() {
setInterval,
);
+ createTimer(syncInteractiveAreaBounds, 250, setInterval);
+
createEffect(() => {
if (
state().variant === "stopped" &&