From 35391073ea12f3497cff75f66752d897eb8f2b3b Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 26 May 2026 19:47:34 +0530 Subject: [PATCH 01/10] fix(desktop): restore last confirmed capture area when reopening selector --- apps/desktop/src/routes/capture-area.tsx | 40 ++++++++++++++---------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index 0a2a3feeb17..a081cf0bdb4 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -72,11 +72,18 @@ export default function CaptureArea() { const { rawOptions, setOptions } = createOptionsQuery(); - const hasStoredSelection = createMemo(() => { + const activeScreenId = createMemo(() => { const target = rawOptions.captureTarget; - if (target.variant !== "display") return false; + if (target.variant === "display") return target.id; + if (target.variant === "area") return target.screen; + return null; + }); + + const hasStoredSelection = createMemo(() => { + const screenId = activeScreenId(); + if (!screenId) return false; return ( - state.lastSelectedBounds?.some((entry) => entry.screenId === target.id) ?? + state.lastSelectedBounds?.some((entry) => entry.screenId === screenId) ?? false ); }); @@ -90,22 +97,22 @@ export default function CaptureArea() { ) return; - const target = rawOptions.captureTarget; - if (target.variant !== "display") return; + const screenId = activeScreenId(); + if (!screenId) return; const existingIndex = state.lastSelectedBounds?.findIndex( - (item) => item.screenId === target.id, + (item) => item.screenId === screenId, ); if (existingIndex >= 0) { setState("lastSelectedBounds", existingIndex, { - screenId: target.id, + screenId, bounds: currentBounds, }); } else { setState("lastSelectedBounds", [ ...state.lastSelectedBounds, - { screenId: target.id, bounds: currentBounds }, + { screenId, bounds: currentBounds }, ]); } @@ -114,7 +121,7 @@ export default function CaptureArea() { "captureTarget", reconcile({ variant: "area", - screen: target.id, + screen: screenId, bounds: { position: { x: b.x, y: b.y }, size: { width: b.width, height: b.height }, @@ -148,10 +155,10 @@ export default function CaptureArea() { cropperRef?.reset(); setAspect(null); - const target = rawOptions.captureTarget; - if (target.variant !== "display") return; + const screenId = activeScreenId(); + if (!screenId) return; setState("lastSelectedBounds", (values) => - values?.filter((v) => v.screenId !== target.id), + values?.filter((v) => v.screenId !== screenId), ); } @@ -287,12 +294,11 @@ export default function CaptureArea() { onCropChange={setCrop} snapToRatioEnabled={state.snapToRatio} initialCrop={() => { - const target = rawOptions.captureTarget; - if (target.variant === "display") + const screenId = activeScreenId(); + if (screenId) return ( - state.lastSelectedBounds?.find( - (m) => m.screenId === target.id, - )?.bounds ?? CROP_ZERO + state.lastSelectedBounds?.find((m) => m.screenId === screenId) + ?.bounds ?? CROP_ZERO ); return CROP_ZERO; }} From d931863d42a2bfc45fe74653736f9803ef099372 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 26 May 2026 20:40:17 +0530 Subject: [PATCH 02/10] fix(desktop): restore last confirmed capture area when reopening selector --- apps/desktop/src/routes/capture-area.tsx | 51 ++++++++++++------- .../src/routes/target-select-overlay.tsx | 23 ++++++++- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index a081cf0bdb4..79abf24391c 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -8,7 +8,15 @@ import { WebviewWindow, } from "@tauri-apps/api/webviewWindow"; import { type as ostype } from "@tauri-apps/plugin-os"; -import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"; +import { + createEffect, + createMemo, + createSignal, + onCleanup, + onMount, + Show, + untrack, +} from "solid-js"; import { createStore, reconcile } from "solid-js/store"; import { Transition } from "solid-transition-group"; import { @@ -79,12 +87,21 @@ export default function CaptureArea() { return null; }); + // Frozen on first non-null value (from synchronous localStorage load) so that + // later async Tauri-store updates carrying stale data cannot reset it. + const [screenId, setScreenId] = createSignal( + untrack(activeScreenId), + ); + createEffect(() => { + const id = activeScreenId(); + if (id && !screenId()) setScreenId(id); + }); + const hasStoredSelection = createMemo(() => { - const screenId = activeScreenId(); - if (!screenId) return false; + const id = screenId(); + if (!id) return false; return ( - state.lastSelectedBounds?.some((entry) => entry.screenId === screenId) ?? - false + state.lastSelectedBounds?.some((entry) => entry.screenId === id) ?? false ); }); @@ -97,22 +114,22 @@ export default function CaptureArea() { ) return; - const screenId = activeScreenId(); - if (!screenId) return; + const id = screenId(); + if (!id) return; const existingIndex = state.lastSelectedBounds?.findIndex( - (item) => item.screenId === screenId, + (item) => item.screenId === id, ); if (existingIndex >= 0) { setState("lastSelectedBounds", existingIndex, { - screenId, + screenId: id, bounds: currentBounds, }); } else { setState("lastSelectedBounds", [ ...state.lastSelectedBounds, - { screenId, bounds: currentBounds }, + { screenId: id, bounds: currentBounds }, ]); } @@ -121,7 +138,7 @@ export default function CaptureArea() { "captureTarget", reconcile({ variant: "area", - screen: screenId, + screen: id, bounds: { position: { x: b.x, y: b.y }, size: { width: b.width, height: b.height }, @@ -155,10 +172,10 @@ export default function CaptureArea() { cropperRef?.reset(); setAspect(null); - const screenId = activeScreenId(); - if (!screenId) return; + const id = screenId(); + if (!id) return; setState("lastSelectedBounds", (values) => - values?.filter((v) => v.screenId !== screenId), + values?.filter((v) => v.screenId !== id), ); } @@ -294,10 +311,10 @@ export default function CaptureArea() { onCropChange={setCrop} snapToRatioEnabled={state.snapToRatio} initialCrop={() => { - const screenId = activeScreenId(); - if (screenId) + const id = screenId(); + if (id) return ( - state.lastSelectedBounds?.find((m) => m.screenId === screenId) + state.lastSelectedBounds?.find((m) => m.screenId === id) ?.bounds ?? CROP_ZERO ); return CROP_ZERO; diff --git a/apps/desktop/src/routes/target-select-overlay.tsx b/apps/desktop/src/routes/target-select-overlay.tsx index eb64f497ead..41fc68fe46a 100644 --- a/apps/desktop/src/routes/target-select-overlay.tsx +++ b/apps/desktop/src/routes/target-select-overlay.tsx @@ -234,6 +234,25 @@ function Inner() { CropBounds | undefined >(undefined); + const effectiveInitialBounds = createMemo(() => { + const explicit = initialAreaBounds(); + if (explicit !== undefined) return explicit; + const target = options.captureTarget; + if ( + target.variant === "area" && + params.displayId && + target.screen === params.displayId + ) { + return { + x: target.bounds.position.x, + y: target.bounds.position.y, + width: target.bounds.size.width, + height: target.bounds.size.height, + }; + } + return undefined; + }); + createEffect(() => { const target = options.captureTarget; if ( @@ -725,7 +744,7 @@ function Inner() { () => isInteracting() || isActiveDisplay(), ); const shouldShowSelectionHint = createMemo(() => { - if (initialAreaBounds() !== undefined) return false; + if (effectiveInitialBounds() !== undefined) return false; if (!isActiveDisplay()) return false; const bounds = crop(); return bounds.width <= 1 && bounds.height <= 1 && !isInteracting(); @@ -1146,7 +1165,7 @@ function Inner() { ref={cropperRef} onInteraction={setIsInteracting} onCropChange={setCrop} - initialCrop={() => initialAreaBounds() ?? CROP_ZERO} + initialCrop={() => effectiveInitialBounds() ?? CROP_ZERO} showBounds={isValid()} aspectRatio={aspect() ?? undefined} snapToRatioEnabled={snapToRatioEnabled()} From 146e06f1cd5f954b80f9eef8a8005351306b17c1 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 26 May 2026 21:09:50 +0530 Subject: [PATCH 03/10] =?UTF-8?q?fix(desktop):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20untrack=20screenId=20in=20effect,=20fallback=20init?= =?UTF-8?q?ialCrop=20to=20captureTarget=20bounds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/routes/capture-area.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index 79abf24391c..5467621f8e7 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -94,7 +94,7 @@ export default function CaptureArea() { ); createEffect(() => { const id = activeScreenId(); - if (id && !screenId()) setScreenId(id); + if (id && !untrack(screenId)) setScreenId(id); }); const hasStoredSelection = createMemo(() => { @@ -312,11 +312,21 @@ export default function CaptureArea() { snapToRatioEnabled={state.snapToRatio} initialCrop={() => { const id = screenId(); - if (id) - return ( - state.lastSelectedBounds?.find((m) => m.screenId === id) - ?.bounds ?? CROP_ZERO - ); + if (id) { + const stored = state.lastSelectedBounds?.find( + (m) => m.screenId === id, + )?.bounds; + if (stored) return stored; + const target = rawOptions.captureTarget; + if (target.variant === "area" && target.screen === id) { + return { + x: target.bounds.position.x, + y: target.bounds.position.y, + width: target.bounds.size.width, + height: target.bounds.size.height, + }; + } + } return CROP_ZERO; }} onContextMenu={(e) => showCropOptionsMenu(e, true)} From 63869d94cadcb61f72bdb9c0a489c4824e457656 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 21:55:12 +0530 Subject: [PATCH 04/10] fix(desktop): simplify screenId to reactive memo, fix hasStoredSelection for area variant --- apps/desktop/src/routes/capture-area.tsx | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index 5467621f8e7..ebd0f7ef45c 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -8,15 +8,7 @@ import { WebviewWindow, } from "@tauri-apps/api/webviewWindow"; import { type as ostype } from "@tauri-apps/plugin-os"; -import { - createEffect, - createMemo, - createSignal, - onCleanup, - onMount, - Show, - untrack, -} from "solid-js"; +import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"; import { createStore, reconcile } from "solid-js/store"; import { Transition } from "solid-transition-group"; import { @@ -87,19 +79,12 @@ export default function CaptureArea() { return null; }); - // Frozen on first non-null value (from synchronous localStorage load) so that - // later async Tauri-store updates carrying stale data cannot reset it. - const [screenId, setScreenId] = createSignal( - untrack(activeScreenId), - ); - createEffect(() => { - const id = activeScreenId(); - if (id && !untrack(screenId)) setScreenId(id); - }); + const screenId = activeScreenId; const hasStoredSelection = createMemo(() => { const id = screenId(); if (!id) return false; + if (rawOptions.captureTarget.variant === "area") return true; return ( state.lastSelectedBounds?.some((entry) => entry.screenId === id) ?? false ); From 0f2d5ba1d6a2b43e96fe427cf3ed9e567756c743 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 22:00:02 +0530 Subject: [PATCH 05/10] fix(desktop): guard hasStoredSelection area check against empty bounds and wrong screen --- apps/desktop/src/routes/capture-area.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index ebd0f7ef45c..8397438034e 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -84,7 +84,10 @@ export default function CaptureArea() { const hasStoredSelection = createMemo(() => { const id = screenId(); if (!id) return false; - if (rawOptions.captureTarget.variant === "area") return true; + const target = rawOptions.captureTarget; + if (target.variant === "area" && target.screen === id) { + return target.bounds.size.width > 1 && target.bounds.size.height > 1; + } return ( state.lastSelectedBounds?.some((entry) => entry.screenId === id) ?? false ); From e1557b2f11bc44311589fdcde0cb29f7f5935898 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 22:03:02 +0530 Subject: [PATCH 06/10] fix(desktop): skip degenerate area bounds in effectiveInitialBounds to keep selection hint visible --- apps/desktop/src/routes/target-select-overlay.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/routes/target-select-overlay.tsx b/apps/desktop/src/routes/target-select-overlay.tsx index 41fc68fe46a..81eade376e1 100644 --- a/apps/desktop/src/routes/target-select-overlay.tsx +++ b/apps/desktop/src/routes/target-select-overlay.tsx @@ -243,11 +243,13 @@ function Inner() { params.displayId && target.screen === params.displayId ) { + const { width, height } = target.bounds.size; + if (width <= 1 || height <= 1) return undefined; return { x: target.bounds.position.x, y: target.bounds.position.y, - width: target.bounds.size.width, - height: target.bounds.size.height, + width, + height, }; } return undefined; From 4de6f6dccec308ba9a706d23b30fadda8b69bcc3 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 22:07:43 +0530 Subject: [PATCH 07/10] fix(desktop): check lastSelectedBounds before captureTarget fallback in hasStoredSelection --- apps/desktop/src/routes/capture-area.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index 8397438034e..1c8965895e9 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -84,13 +84,15 @@ export default function CaptureArea() { const hasStoredSelection = createMemo(() => { const id = screenId(); if (!id) return false; + + if (state.lastSelectedBounds?.some((entry) => entry.screenId === id)) + return true; + const target = rawOptions.captureTarget; if (target.variant === "area" && target.screen === id) { return target.bounds.size.width > 1 && target.bounds.size.height > 1; } - return ( - state.lastSelectedBounds?.some((entry) => entry.screenId === id) ?? false - ); + return false; }); async function handleConfirm() { From dc13f2b9a73815ef57924f5cab7e5fd0b3abba32 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 22:41:20 +0530 Subject: [PATCH 08/10] fix(desktop): zero captureTarget bounds on reset to prevent fallback from resurrecting cleared selection --- apps/desktop/src/routes/capture-area.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index 1c8965895e9..f35429e7033 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -167,6 +167,21 @@ export default function CaptureArea() { setState("lastSelectedBounds", (values) => values?.filter((v) => v.screenId !== id), ); + + const target = rawOptions.captureTarget; + if (target.variant === "area" && target.screen === id) { + setOptions( + "captureTarget", + reconcile({ + variant: "area", + screen: id, + bounds: { + position: { x: 0, y: 0 }, + size: { width: 0, height: 0 }, + }, + }), + ); + } } async function showCropOptionsMenu(e: UIEvent, positionAtCursor = false) { From 9ed93e9fa2c2f50552f1717b00845898a1717c99 Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 22:46:08 +0530 Subject: [PATCH 09/10] fix(desktop): prefer captureTarget bounds over lastSelectedBounds in initialCrop for freshest selection --- apps/desktop/src/routes/capture-area.tsx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index f35429e7033..c3e57ebaa94 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -317,22 +317,25 @@ export default function CaptureArea() { snapToRatioEnabled={state.snapToRatio} initialCrop={() => { const id = screenId(); - if (id) { - const stored = state.lastSelectedBounds?.find( - (m) => m.screenId === id, - )?.bounds; - if (stored) return stored; - const target = rawOptions.captureTarget; - if (target.variant === "area" && target.screen === id) { + if (!id) return CROP_ZERO; + + const target = rawOptions.captureTarget; + if (target.variant === "area" && target.screen === id) { + const { width, height } = target.bounds.size; + if (width > 1 && height > 1) { return { x: target.bounds.position.x, y: target.bounds.position.y, - width: target.bounds.size.width, - height: target.bounds.size.height, + width, + height, }; } } - return CROP_ZERO; + + return ( + state.lastSelectedBounds?.find((m) => m.screenId === id) + ?.bounds ?? CROP_ZERO + ); }} onContextMenu={(e) => showCropOptionsMenu(e, true)} /> From 842d38d5fdbd993736a2c9a6bd0f763738f3726d Mon Sep 17 00:00:00 2001 From: ManthanNimodiya Date: Tue, 16 Jun 2026 23:00:20 +0530 Subject: [PATCH 10/10] fix(desktop): move cropperRef.reset after state clear, switch captureTarget to display on reset --- apps/desktop/src/routes/capture-area.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/routes/capture-area.tsx b/apps/desktop/src/routes/capture-area.tsx index c3e57ebaa94..2c62a6adaca 100644 --- a/apps/desktop/src/routes/capture-area.tsx +++ b/apps/desktop/src/routes/capture-area.tsx @@ -159,7 +159,6 @@ export default function CaptureArea() { const [aspect, setAspect] = createSignal(null); function reset() { - cropperRef?.reset(); setAspect(null); const id = screenId(); @@ -170,18 +169,10 @@ export default function CaptureArea() { const target = rawOptions.captureTarget; if (target.variant === "area" && target.screen === id) { - setOptions( - "captureTarget", - reconcile({ - variant: "area", - screen: id, - bounds: { - position: { x: 0, y: 0 }, - size: { width: 0, height: 0 }, - }, - }), - ); + setOptions("captureTarget", reconcile({ variant: "display", id })); } + + cropperRef?.reset(); } async function showCropOptionsMenu(e: UIEvent, positionAtCursor = false) {