fix(wake-lock): allow display sleep during active work
Prevent idle system sleep on supported desktop runtimes without intentionally keeping the display awake. Narrow wake-lock activation to true active work states and drop the web screen-wake fallback where the platform cannot provide system-sleep-only behavior.
This commit is contained in:
@@ -50,7 +50,7 @@ import {
|
||||
updateSessionModel,
|
||||
} from "./stores/sessions"
|
||||
|
||||
import { getInstanceSessionIndicatorStatus } from "./stores/session-status"
|
||||
import { hasWakeLockEligibleWork } from "./stores/session-status"
|
||||
import { openSettings } from "./stores/settings-screen"
|
||||
import {
|
||||
closeSidecarTab,
|
||||
@@ -204,8 +204,7 @@ const App: Component = () => {
|
||||
const shouldHoldWakeLock = createMemo(() => {
|
||||
const map = instances()
|
||||
for (const id of map.keys()) {
|
||||
const status = getInstanceSessionIndicatorStatus(id)
|
||||
if (status !== "idle") {
|
||||
if (hasWakeLockEligibleWork(id)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,9 @@ export function extractDiagnostics(state: ToolState | undefined): DiagnosticEntr
|
||||
const diagnosticsMap = metadata?.diagnostics as DiagnosticsMap | undefined
|
||||
if (!diagnosticsMap) return []
|
||||
|
||||
return buildDiagnosticEntries(diagnosticsMap, [input.filePath, metadata.filePath, metadata.filepath, input.path])
|
||||
return buildDiagnosticEntries(diagnosticsMap, [input.filePath, metadata.filePath, metadata.filepath, input.path].map((value) =>
|
||||
typeof value === "string" ? value : undefined,
|
||||
))
|
||||
}
|
||||
|
||||
export function resolveDiagnosticsKey(diagnostics: DiagnosticsMap, preferredPaths: Array<string | undefined>): string | undefined {
|
||||
|
||||
@@ -9,51 +9,6 @@ let inFlight: Promise<boolean> | null = null
|
||||
|
||||
let applied = false
|
||||
|
||||
let webWakeLock: any = null
|
||||
|
||||
async function setWebWakeLock(enabled: boolean): Promise<boolean> {
|
||||
if (typeof navigator === "undefined") return false
|
||||
|
||||
const api = (navigator as any).wakeLock
|
||||
if (!api?.request) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
if (enabled) {
|
||||
if (webWakeLock) {
|
||||
return true
|
||||
}
|
||||
webWakeLock = await api.request("screen")
|
||||
try {
|
||||
webWakeLock.addEventListener?.("release", () => {
|
||||
// If the lock is released by the UA (e.g., tab hidden), clear local state.
|
||||
webWakeLock = null
|
||||
if (desired) {
|
||||
// Re-acquire best-effort.
|
||||
queueMicrotask(() => {
|
||||
void setWakeLockDesired(true)
|
||||
})
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
// optional
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (webWakeLock) {
|
||||
await webWakeLock.release?.()
|
||||
}
|
||||
webWakeLock = null
|
||||
return false
|
||||
} catch (error) {
|
||||
log.log("[wake-lock] web wake lock failed", error)
|
||||
webWakeLock = null
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function hasAnyWakeLockSupport(): boolean {
|
||||
if (typeof window === "undefined") return false
|
||||
if (isElectronHost()) {
|
||||
@@ -63,7 +18,7 @@ function hasAnyWakeLockSupport(): boolean {
|
||||
if (isTauriHost()) {
|
||||
return typeof window.__TAURI__?.core?.invoke === "function"
|
||||
}
|
||||
return Boolean((navigator as any)?.wakeLock?.request)
|
||||
return false
|
||||
}
|
||||
|
||||
async function setElectronWakeLock(enabled: boolean): Promise<boolean> {
|
||||
@@ -89,9 +44,7 @@ async function setTauriWakeLock(enabled: boolean): Promise<boolean> {
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
// Match Electron's prevent-display-sleep behavior by keeping the display
|
||||
// awake without blocking explicit system sleep requests.
|
||||
await invoke("wake_lock_start", { config: { display: true, idle: false, sleep: false } })
|
||||
await invoke("wake_lock_start", { config: { display: false, idle: true, sleep: false } })
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -108,17 +61,15 @@ async function applyWakeLock(enabled: boolean): Promise<boolean> {
|
||||
|
||||
if (isElectronHost()) {
|
||||
const ok = await setElectronWakeLock(enabled)
|
||||
if (ok || !enabled) return ok
|
||||
// fallback to web API if electron preload didn't expose it
|
||||
return ok
|
||||
}
|
||||
|
||||
if (isTauriHost()) {
|
||||
const ok = await setTauriWakeLock(enabled)
|
||||
if (ok || !enabled) return ok
|
||||
// fallback to web API if tauri command isn't available
|
||||
return ok
|
||||
}
|
||||
|
||||
return await setWebWakeLock(enabled)
|
||||
return false
|
||||
}
|
||||
|
||||
export function setWakeLockDesired(nextDesired: boolean): Promise<boolean> {
|
||||
|
||||
20
packages/ui/src/stores/session-status.test.ts
Normal file
20
packages/ui/src/stores/session-status.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
|
||||
import { shouldSessionHoldWakeLock } from "./wake-lock-eligibility.ts"
|
||||
|
||||
describe("shouldSessionHoldWakeLock", () => {
|
||||
it("holds wake lock only for qualifying active work", () => {
|
||||
assert.equal(shouldSessionHoldWakeLock({ status: "working", pendingPermission: false, pendingQuestion: false }), true)
|
||||
assert.equal(
|
||||
shouldSessionHoldWakeLock({ status: "compacting", pendingPermission: false, pendingQuestion: false }),
|
||||
true,
|
||||
)
|
||||
assert.equal(shouldSessionHoldWakeLock({ status: "idle", pendingPermission: false, pendingQuestion: false }), false)
|
||||
})
|
||||
|
||||
it("does not hold wake lock while waiting for permission or input", () => {
|
||||
assert.equal(shouldSessionHoldWakeLock({ status: "working", pendingPermission: true, pendingQuestion: false }), false)
|
||||
assert.equal(shouldSessionHoldWakeLock({ status: "working", pendingPermission: false, pendingQuestion: true }), false)
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,27 @@
|
||||
import type { Session, SessionRetryState, SessionStatus } from "../types/session"
|
||||
import { getInstanceSessionIndicatorStatusCached, sessions } from "./session-state"
|
||||
import { shouldSessionHoldWakeLock } from "./wake-lock-eligibility"
|
||||
|
||||
function getSession(instanceId: string, sessionId: string): Session | null {
|
||||
const instanceSessions = sessions().get(instanceId)
|
||||
return instanceSessions?.get(sessionId) ?? null
|
||||
}
|
||||
|
||||
export function hasWakeLockEligibleWork(instanceId: string): boolean {
|
||||
const instanceSessions = sessions().get(instanceId)
|
||||
if (!instanceSessions) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const session of instanceSessions.values()) {
|
||||
if (shouldSessionHoldWakeLock(session)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function getSessionStatus(instanceId: string, sessionId: string): SessionStatus {
|
||||
const session = getSession(instanceId, sessionId)
|
||||
if (!session) {
|
||||
|
||||
11
packages/ui/src/stores/wake-lock-eligibility.ts
Normal file
11
packages/ui/src/stores/wake-lock-eligibility.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Session } from "../types/session"
|
||||
|
||||
export function shouldSessionHoldWakeLock(
|
||||
session: Pick<Session, "status" | "pendingPermission" | "pendingQuestion">,
|
||||
): boolean {
|
||||
if (session.pendingPermission || session.pendingQuestion) {
|
||||
return false
|
||||
}
|
||||
|
||||
return session.status === "working" || session.status === "compacting"
|
||||
}
|
||||
Reference in New Issue
Block a user