- {formLabel}
+
+ {formLabel}
+ {tooltip && (
+
+
+
+ )}
+
{description && {description}}
+ {tooltip && (
+
+ )}
{hasPreview && (
diff --git a/packages/extension/src/services/option/defaultSettings.ts b/packages/extension/src/services/option/defaultSettings.ts
index 46c804bc..0caa3772 100644
--- a/packages/extension/src/services/option/defaultSettings.ts
+++ b/packages/extension/src/services/option/defaultSettings.ts
@@ -36,7 +36,6 @@ export const emptySettings: SettingsType = {
alignOffset: 0,
sideOffset: 0,
},
- popupAutoCloseDelay: undefined,
linkCommand: {
enabled: LINK_COMMAND_ENABLED.ENABLE,
openMode: DRAG_OPEN_MODE.PREVIEW_POPUP,
@@ -57,6 +56,7 @@ export const emptySettings: SettingsType = {
shortcuts: { shortcuts: [] },
windowOption: {
sidePanelAutoHide: false,
+ popupAutoCloseDelay: undefined,
},
}
diff --git a/packages/extension/src/services/popupAutoClose.test.ts b/packages/extension/src/services/popupAutoClose.test.ts
index ffbcc382..63566ba5 100644
--- a/packages/extension/src/services/popupAutoClose.test.ts
+++ b/packages/extension/src/services/popupAutoClose.test.ts
@@ -1,15 +1,27 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+
+// Mock all dependencies before importing PopupAutoClose
+vi.mock("@/services/settings/enhancedSettings", () => ({
+ enhancedSettings: {
+ getSection: vi.fn(),
+ },
+}))
+vi.mock("@/services/settings/settingsCache")
+vi.mock("@/services/chrome", () => ({
+ closeWindow: vi.fn(),
+}))
+vi.mock("@/services/windowStackManager", () => ({
+ WindowStackManager: {
+ removeWindow: vi.fn(),
+ },
+}))
+
import { PopupAutoClose } from "./popupAutoClose"
-import { Settings } from "@/services/settings/settings"
+import { enhancedSettings } from "@/services/settings/enhancedSettings"
import { closeWindow } from "@/services/chrome"
import { WindowStackManager } from "@/services/windowStackManager"
-// Mock dependencies
-vi.mock("@/services/settings/settings")
-vi.mock("@/services/chrome")
-vi.mock("@/services/windowStackManager")
-
-const mockSettings = vi.mocked(Settings)
+const mockEnhancedSettings = vi.mocked(enhancedSettings)
const mockCloseWindow = vi.mocked(closeWindow)
const mockWindowStackManager = vi.mocked(WindowStackManager)
@@ -34,8 +46,10 @@ describe("PopupAutoClose", () => {
it("should close windows immediately when delay is undefined", async () => {
const windows = [{ id: 100, commandId: "test", srcWindowId: 1 }]
- mockSettings.get.mockResolvedValue({
- popupAutoCloseDelay: undefined,
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: undefined,
+ },
} as any)
await PopupAutoClose.scheduleClose(windows)
@@ -48,8 +62,10 @@ describe("PopupAutoClose", () => {
it("should close windows immediately when delay is 0", async () => {
const windows = [{ id: 100, commandId: "test", srcWindowId: 1 }]
- mockSettings.get.mockResolvedValue({
- popupAutoCloseDelay: 0,
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: 0,
+ },
} as any)
await PopupAutoClose.scheduleClose(windows)
@@ -63,8 +79,10 @@ describe("PopupAutoClose", () => {
const delay = 1000
const windows = [{ id: 100, commandId: "test", srcWindowId: 1 }]
- mockSettings.get.mockResolvedValue({
- popupAutoCloseDelay: delay,
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: delay,
+ },
} as any)
await PopupAutoClose.scheduleClose(windows)
@@ -86,8 +104,10 @@ describe("PopupAutoClose", () => {
{ id: 101, commandId: "test2", srcWindowId: 1 },
]
- mockSettings.get.mockResolvedValue({
- popupAutoCloseDelay: undefined,
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: undefined,
+ },
} as any)
await PopupAutoClose.scheduleClose(windows)
@@ -102,8 +122,8 @@ describe("PopupAutoClose", () => {
it("should do nothing when no windows to close", async () => {
await PopupAutoClose.scheduleClose([])
- // Should not call Settings.get or closeWindow
- expect(mockSettings.get).not.toHaveBeenCalled()
+ // Should not call enhancedSettings.getSection or closeWindow
+ expect(mockEnhancedSettings.getSection).not.toHaveBeenCalled()
expect(mockCloseWindow).not.toHaveBeenCalled()
})
@@ -112,8 +132,10 @@ describe("PopupAutoClose", () => {
const windows1 = [{ id: 100, commandId: "test1", srcWindowId: 1 }]
const windows2 = [{ id: 101, commandId: "test2", srcWindowId: 1 }]
- mockSettings.get.mockResolvedValue({
- popupAutoCloseDelay: delay,
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: delay,
+ },
} as any)
// Schedule first close
diff --git a/packages/extension/src/services/popupAutoClose.ts b/packages/extension/src/services/popupAutoClose.ts
index 341bf34e..cdbb64ac 100644
--- a/packages/extension/src/services/popupAutoClose.ts
+++ b/packages/extension/src/services/popupAutoClose.ts
@@ -1,4 +1,5 @@
-import { Settings } from "@/services/settings/settings"
+import { enhancedSettings } from "@/services/settings/enhancedSettings"
+import { CACHE_SECTIONS } from "@/services/settings/settingsCache"
import { closeWindow } from "@/services/chrome"
import { WindowStackManager } from "@/services/windowStackManager"
import type { WindowType } from "@/types"
@@ -35,8 +36,10 @@ export class PopupAutoClose {
}
// Get the auto-close delay setting
- const settings = await Settings.get()
- const autoCloseDelay = settings.popupAutoCloseDelay
+ const userSettings = await enhancedSettings.getSection(
+ CACHE_SECTIONS.USER_SETTINGS,
+ )
+ const autoCloseDelay = userSettings.windowOption.popupAutoCloseDelay
// Define the close function
const closeWindows = async () => {
diff --git a/packages/extension/src/types/index.ts b/packages/extension/src/types/index.ts
index d2a9ab4b..e7f3b96d 100644
--- a/packages/extension/src/types/index.ts
+++ b/packages/extension/src/types/index.ts
@@ -79,6 +79,7 @@ export type CopyOption = "default" | "text"
type WindowOption = {
sidePanelAutoHide: boolean
+ popupAutoCloseDelay?: number
}
type LinkCommandStartupMethod = {
@@ -169,7 +170,6 @@ export type UserSettings = {
settingVersion: Version
startupMethod: StartupMethod
popupPlacement: PopupPlacement
- popupAutoCloseDelay?: number
commands: Array
linkCommand: LinkCommandSettings
folders: Array
From bb9e0e5e1c7d52bbdee6c8a2a819162efc6fbf16 Mon Sep 17 00:00:00 2001
From: ujiro99
Date: Sat, 21 Feb 2026 17:30:58 +0900
Subject: [PATCH 11/14] Update: Add delay even in onHidden.
---
packages/extension/src/background_script.ts | 8 +++++---
packages/shared/tsconfig.tsbuildinfo | 2 +-
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/packages/extension/src/background_script.ts b/packages/extension/src/background_script.ts
index 2d463874..e53955fc 100644
--- a/packages/extension/src/background_script.ts
+++ b/packages/extension/src/background_script.ts
@@ -288,9 +288,11 @@ const commandFuncs = {
return
}
- // Remove the window.
- await closeWindow(windowId, "onHidden")
- await WindowStackManager.removeWindow(windowId)
+ // Schedule popup window to close with configured delay
+ const window = layer.find((w) => w.id === windowId)
+ if (window) {
+ await PopupAutoClose.scheduleClose([window])
+ }
response(false)
}
diff --git a/packages/shared/tsconfig.tsbuildinfo b/packages/shared/tsconfig.tsbuildinfo
index b3c515d0..64f82187 100644
--- a/packages/shared/tsconfig.tsbuildinfo
+++ b/packages/shared/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/index.ts","./src/constants/index.ts","./src/constants/open-mode.ts","./src/types/command.ts","./src/types/common.ts","./src/types/index.ts","./src/utils/cn.ts","./src/utils/common.ts","./src/utils/index.ts","./src/utils/type-guards.ts"],"version":"5.6.3"}
\ No newline at end of file
+{"root":["./src/index.ts","./src/constants/index.ts","./src/constants/open-mode.ts","./src/types/command.ts","./src/types/common.ts","./src/types/index.ts","./src/utils/cn.ts","./src/utils/common.ts","./src/utils/index.ts","./src/utils/type-guards.ts"],"version":"5.9.2"}
\ No newline at end of file
From e1623afd0c87d0c06d203b04e0f8e27c3f30338b Mon Sep 17 00:00:00 2001
From: ujiro99
Date: Sun, 22 Feb 2026 14:29:04 +0900
Subject: [PATCH 12/14] Update
packages/extension/public/_locales/en/messages.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
packages/extension/public/_locales/en/messages.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/extension/public/_locales/en/messages.json b/packages/extension/public/_locales/en/messages.json
index 86f4ea4f..fc3f0541 100644
--- a/packages/extension/public/_locales/en/messages.json
+++ b/packages/extension/public/_locales/en/messages.json
@@ -207,7 +207,7 @@
"message": "Popup Auto-Close Delay"
},
"Option_popupAutoCloseDelay_desc": {
- "message": "Set the delay time before the popup automatically closes after losing focus. Set to 0 or leave empty for immediate close."
+ "message": "Set the delay time before the popup automatically closes after losing focus. Set to 0 or leave empty for immediate close.\nMaximum: 10000 ms"
},
"Option_popupAutoCloseDelay_placeholder": {
"message": "0 (close immediately)"
From 9ca073f17544330ac983d92ad375bdb20f6587f0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 05:42:29 +0000
Subject: [PATCH 13/14] Fix timer type, add reason parameter, error handling,
and use 0 as default
Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com>
---
packages/extension/src/background_script.ts | 2 +-
.../src/services/option/defaultSettings.ts | 3 +-
.../src/services/popupAutoClose.test.ts | 38 ++++++++++++++++++-
.../extension/src/services/popupAutoClose.ts | 19 +++++++---
4 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/packages/extension/src/background_script.ts b/packages/extension/src/background_script.ts
index e53955fc..d6eb43eb 100644
--- a/packages/extension/src/background_script.ts
+++ b/packages/extension/src/background_script.ts
@@ -291,7 +291,7 @@ const commandFuncs = {
// Schedule popup window to close with configured delay
const window = layer.find((w) => w.id === windowId)
if (window) {
- await PopupAutoClose.scheduleClose([window])
+ await PopupAutoClose.scheduleClose([window], "onHidden")
}
response(false)
}
diff --git a/packages/extension/src/services/option/defaultSettings.ts b/packages/extension/src/services/option/defaultSettings.ts
index 0caa3772..1f8533c1 100644
--- a/packages/extension/src/services/option/defaultSettings.ts
+++ b/packages/extension/src/services/option/defaultSettings.ts
@@ -56,7 +56,7 @@ export const emptySettings: SettingsType = {
shortcuts: { shortcuts: [] },
windowOption: {
sidePanelAutoHide: false,
- popupAutoCloseDelay: undefined,
+ popupAutoCloseDelay: 0,
},
}
@@ -159,6 +159,7 @@ export default {
},
windowOption: {
sidePanelAutoHide: false,
+ popupAutoCloseDelay: 0,
},
} as UserSettings
diff --git a/packages/extension/src/services/popupAutoClose.test.ts b/packages/extension/src/services/popupAutoClose.test.ts
index 63566ba5..4882b381 100644
--- a/packages/extension/src/services/popupAutoClose.test.ts
+++ b/packages/extension/src/services/popupAutoClose.test.ts
@@ -162,5 +162,41 @@ describe("PopupAutoClose", () => {
// Now second window should be closed
expect(mockCloseWindow).toHaveBeenCalledWith(101, "onFocusChanged")
})
+
+ it("should use custom reason when provided", async () => {
+ const windows = [{ id: 100, commandId: "test", srcWindowId: 1 }]
+
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: 0,
+ },
+ } as any)
+
+ await PopupAutoClose.scheduleClose(windows, "onHidden")
+
+ // Should close with custom reason
+ expect(mockCloseWindow).toHaveBeenCalledWith(100, "onHidden")
+ })
+
+ it("should handle errors and still clear timer", async () => {
+ const windows = [
+ { id: 100, commandId: "test1", srcWindowId: 1 },
+ { id: 101, commandId: "test2", srcWindowId: 1 },
+ ]
+
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: 0,
+ },
+ } as any)
+
+ // Make first closeWindow call fail
+ mockCloseWindow.mockRejectedValueOnce(new Error("Close failed"))
+
+ await expect(PopupAutoClose.scheduleClose(windows)).rejects.toThrow("Close failed")
+
+ // Timer should still be cleared even after error
+ expect(mockCloseWindow).toHaveBeenCalledTimes(1)
+ })
})
-})
+})
\ No newline at end of file
diff --git a/packages/extension/src/services/popupAutoClose.ts b/packages/extension/src/services/popupAutoClose.ts
index cdbb64ac..15f69c73 100644
--- a/packages/extension/src/services/popupAutoClose.ts
+++ b/packages/extension/src/services/popupAutoClose.ts
@@ -9,7 +9,7 @@ import type { WindowType } from "@/types"
* Manages automatic closing of popup windows with configurable delay
*/
export class PopupAutoClose {
- private static timer: NodeJS.Timeout | null = null
+ private static timer: ReturnType | null = null
/**
* Cancel any pending auto-close timer
@@ -24,9 +24,13 @@ export class PopupAutoClose {
/**
* Schedule popup windows to close with configured delay
* @param windowsToClose - Array of windows to close
+ * @param reason - Reason for closing (e.g., "onFocusChanged", "onHidden")
* @returns Promise that resolves when windows are closed or timer is set
*/
- static async scheduleClose(windowsToClose: WindowType[]): Promise {
+ static async scheduleClose(
+ windowsToClose: WindowType[],
+ reason: string = "onFocusChanged",
+ ): Promise {
// Cancel any existing timer first
this.cancelTimer()
@@ -43,11 +47,14 @@ export class PopupAutoClose {
// Define the close function
const closeWindows = async () => {
- for (const window of windowsToClose) {
- await closeWindow(window.id, "onFocusChanged")
- await WindowStackManager.removeWindow(window.id)
+ try {
+ for (const window of windowsToClose) {
+ await closeWindow(window.id, reason)
+ await WindowStackManager.removeWindow(window.id)
+ }
+ } finally {
+ this.timer = null
}
- this.timer = null
}
// Execute close based on delay setting
From 9d0bcf009d2ffabe38748a1df94210fea31cbab9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 05:43:37 +0000
Subject: [PATCH 14/14] Use typed CloseReason and improve error handling test
Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com>
---
packages/extension/src/services/popupAutoClose.test.ts | 10 ++++++++--
packages/extension/src/services/popupAutoClose.ts | 7 ++++++-
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/packages/extension/src/services/popupAutoClose.test.ts b/packages/extension/src/services/popupAutoClose.test.ts
index 4882b381..a5159a2a 100644
--- a/packages/extension/src/services/popupAutoClose.test.ts
+++ b/packages/extension/src/services/popupAutoClose.test.ts
@@ -195,8 +195,14 @@ describe("PopupAutoClose", () => {
await expect(PopupAutoClose.scheduleClose(windows)).rejects.toThrow("Close failed")
- // Timer should still be cleared even after error
- expect(mockCloseWindow).toHaveBeenCalledTimes(1)
+ // Timer should still be cleared - verify by scheduling another close
+ mockCloseWindow.mockResolvedValue(undefined)
+ const windows2 = [{ id: 102, commandId: "test3", srcWindowId: 1 }]
+
+ await PopupAutoClose.scheduleClose(windows2)
+
+ // Should work fine, proving timer was cleared
+ expect(mockCloseWindow).toHaveBeenCalledWith(102, "onFocusChanged")
})
})
})
\ No newline at end of file
diff --git a/packages/extension/src/services/popupAutoClose.ts b/packages/extension/src/services/popupAutoClose.ts
index 15f69c73..a1d87bab 100644
--- a/packages/extension/src/services/popupAutoClose.ts
+++ b/packages/extension/src/services/popupAutoClose.ts
@@ -4,6 +4,11 @@ import { closeWindow } from "@/services/chrome"
import { WindowStackManager } from "@/services/windowStackManager"
import type { WindowType } from "@/types"
+/**
+ * Valid reasons for closing popup windows
+ */
+export type CloseReason = "onFocusChanged" | "onHidden"
+
/**
* Popup Auto-Close Manager
* Manages automatic closing of popup windows with configurable delay
@@ -29,7 +34,7 @@ export class PopupAutoClose {
*/
static async scheduleClose(
windowsToClose: WindowType[],
- reason: string = "onFocusChanged",
+ reason: CloseReason = "onFocusChanged",
): Promise {
// Cancel any existing timer first
this.cancelTimer()