Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wework/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pnpm --filter wework ai:verify hover --session <session-path> --selector '[data-
pnpm --filter wework ai:verify pointer-move --session <session-path> --selector 'body'
pnpm --filter wework ai:verify wait-for --session <session-path> --selector '[data-testid="..."]' --text '...'
pnpm --filter wework ai:verify capture --session <session-path> --output <png-path>
pnpm --filter wework ai:verify request-close --session <session-path>
pnpm --filter wework ai:verify close-to-tray --session <session-path>
pnpm --filter wework ai:verify stop --session <session-path>
```
Expand All @@ -65,6 +66,7 @@ pnpm --filter wework ai:verify stop --session <session-path>
- Begin with `snapshot`, use existing `data-testid` selectors, and assert a visible text or stable element after each critical action.
- Execute the complete QA test plan in the isolated Tauri session, including the primary path, relevant boundary and error cases, and recovery. Document the environment, cases run, actual results, and evidence in the change handoff or pull request.
- Use `capture` after the final assertion when a visual verification artifact is required. It renders the current WebView without macOS screen-recording permission.
- Use `request-close` to exercise the native main-window close request and its close-to-tray preference or confirmation flow.
- Use `close-to-tray` only for window-lifecycle verification. It destroys the controlled WebView while leaving the isolated Tauri process running so native reopen behavior can be tested.
- On failure, inspect `app.log`, `executor.log`, and Tauri logs under `test-results/ai-verify/`; do not silently downgrade to mocked verification.

Expand Down
19 changes: 18 additions & 1 deletion wework/e2e/desktop/task-flow.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3639,6 +3639,18 @@ async function verifyBackgroundTaskWindowLifecycle({
const lifecycleScreenshotName = name => `window-lifecycle-${name}`
setPhase('popout-window-lifecycle')
await verifyPopoutWindowLifecycle(control, composerSelector)
setPhase('close-request-without-running-task')
await control.command('requestMainWindowClose', 'body')
await control.command('waitFor', '[data-testid="runtime-task-close-confirm-overlay"]', {
visible: true,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('click', '[data-testid="runtime-task-close-cancel-button"]')
const closeCancelledSnapshot = JSON.parse(await control.command('snapshot', 'body'))
assert.ok(
!closeCancelledSnapshot.testIds.includes('runtime-task-close-confirm-overlay'),
'The close-to-tray prompt remained open after cancelling'
)
setPhase('background-streaming-task')
control.setScenario('window_lifecycle')
await control.command('click', '[data-testid="new-chat-button"]')
Expand Down Expand Up @@ -3703,7 +3715,12 @@ async function verifyBackgroundTaskWindowLifecycle({
'The executor process was not alive before close'
)

await control.command('closeMainWindowToTray', 'body')
await control.command('requestMainWindowClose', 'body')
await control.command('waitFor', '[data-testid="runtime-task-close-confirm-overlay"]', {
visible: true,
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('click', '[data-testid="runtime-task-close-confirm-button"]')
await waitForLogPattern(join(resultDir, `wework-tauri-${app.pid}.log`), /windowWillClose:/)
assert.equal(processIsAlive(app.pid), true, 'Closing to tray terminated the Wework process')
assert.equal(
Expand Down
6 changes: 4 additions & 2 deletions wework/scripts/ai-verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const corsHeaders = {
function usage() {
console.error(`Usage:
pnpm --filter wework ai:verify start
pnpm --filter wework ai:verify <capture|capture-popout|snapshot|click|close-to-tray|dismiss-popout|drag|drop-file|drop-paths|fill|hover|metrics|navigate|paste-paths|pointer-move|press|scroll-into-view|select-text|show-popout|system-drag-drop|wait-for|window-focus-snapshot|text|status|stop> --session PATH [options]
pnpm --filter wework ai:verify <capture|capture-popout|snapshot|click|close-to-tray|request-close|dismiss-popout|drag|drop-file|drop-paths|fill|hover|metrics|navigate|paste-paths|pointer-move|press|scroll-into-view|select-text|show-popout|system-drag-drop|wait-for|window-focus-snapshot|text|status|stop> --session PATH [options]

Options:
--codex-home-initialization true
Expand Down Expand Up @@ -386,6 +386,7 @@ async function main() {
snapshot: 'snapshot',
click: 'click',
'close-to-tray': 'closeMainWindowToTray',
'request-close': 'requestMainWindowClose',
'dismiss-popout': 'dismissPopoutWindow',
drag: 'drag',
'drop-file': 'dropFile',
Expand Down Expand Up @@ -422,7 +423,8 @@ async function main() {
command === 'show-popout' ||
command === 'system-drag-drop' ||
command === 'window-focus-snapshot' ||
command === 'close-to-tray'
command === 'close-to-tray' ||
command === 'request-close'
? 'body'
: null)
if (!selector) throw new Error('--selector is required')
Expand Down
17 changes: 1 addition & 16 deletions wework/src/components/layout/WindowFrameControls.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { describe, expect, test, vi, beforeEach } from 'vitest'
import { invoke } from '@tauri-apps/api/core'
import { WindowFrameControls } from './WindowFrameControls'

const mocks = vi.hoisted(() => {
Expand All @@ -20,10 +19,6 @@ vi.mock('@tauri-apps/api/window', () => ({
getCurrentWindow: vi.fn(() => mocks.windowMock),
}))

vi.mock('@tauri-apps/api/core', () => ({
invoke: vi.fn().mockResolvedValue(undefined),
}))

describe('WindowFrameControls', () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down Expand Up @@ -78,19 +73,9 @@ describe('WindowFrameControls', () => {
expect(mocks.windowMock.maximize).not.toHaveBeenCalled()
})

test('close button invokes close_main_window_to_tray command', async () => {
render(<WindowFrameControls />)
fireEvent.click(screen.getByTestId('window-close-button'))
await waitFor(() => expect(invoke).toHaveBeenCalledWith('close_main_window_to_tray'))
})

test('falls back to window.close when close_main_window_to_tray command fails', async () => {
const invokeMock = invoke as unknown as ReturnType<typeof vi.fn>
invokeMock.mockRejectedValueOnce(new Error('command unavailable'))

test('close button requests the standard window close flow', async () => {
render(<WindowFrameControls />)
fireEvent.click(screen.getByTestId('window-close-button'))
await waitFor(() => expect(invoke).toHaveBeenCalledWith('close_main_window_to_tray'))
await waitFor(() => expect(mocks.windowMock.close).toHaveBeenCalledTimes(1))
})

Expand Down
10 changes: 2 additions & 8 deletions wework/src/components/layout/WindowFrameControls.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Minus, Square, Copy, X } from 'lucide-react'
import { useCallback, useEffect, useState } from 'react'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { invoke } from '@tauri-apps/api/core'
import { useTranslation } from '@/hooks/useTranslation'
import { cn } from '@/lib/utils'

Expand Down Expand Up @@ -60,14 +59,9 @@ export function WindowFrameControls({ className }: { className?: string }) {

const handleClose = useCallback(async () => {
try {
await invoke('close_main_window_to_tray')
await getCurrentWindow().close()
} catch {
// Fallback to direct window close if the command is unavailable
try {
await getCurrentWindow().close()
} catch {
// Ignore
}
// Ignore
}
}, [])

Expand Down
4 changes: 4 additions & 0 deletions wework/src/e2e/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,8 @@ async function executeDesktopControlCommand(command: DesktopControlCommand): Pro
return invoke<string>('capture_popout_webview')
case 'closeMainWindowToTray':
return ''
case 'requestMainWindowClose':
return ''
Comment on lines +728 to +729

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'automation\.ts$' . || true

echo "== file size =="
wc -l wework/src/e2e/automation.ts

echo "== outline relevant regions =="
sed -n '700,745p' wework/src/e2e/automation.ts | cat -n -v
echo "---"
sed -n '1055,1095p' wework/src/e2e/automation.ts | cat -n -v

echo "== command/action definitions and close handling search =="
rg -n "requestMainWindowClose|closeMainWindowToTray|postDesktopControlResult|executeDesktopControlCommand|getCurrentWindow\\(\\)\\.close|Action" wework/src/e2e/automation.ts

Repository: wecode-ai/Wegent

Length of output: 5402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== webviewWindow imports and definitions =="
rg -n "getCurrentWindow|WebviewWindow|DesktopControlCommand|DesktopControlResult" wework/src/e2e/automation.ts wework/src/types/automation.ts

echo "== automation type definitions =="
sed -n '1,180p' wework/src/types/automation.ts | cat -n -v

echo "== imports surrounding automation.ts =="
sed -n '1,45p' wework/src/e2e/automation.ts | cat -n -v

echo "== inspect Tauri API docs package if present =="
fd -a '(^window\.json$|window\.mjs$|tsconfig.*\.json$)' . | sed -n '1,80p'
rg -n "`@tauri-apps/api/webviewWindow`|`@tauri-apps/api/window`|WebviewWindow" -S . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' | sed -n '1,120p'

Repository: wecode-ai/Wegent

Length of output: 13449


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate desktop-control contract files =="
fd -a 'desktop-control|desktop' . | rg 'desktop-control|desktop' | sed -n '1,120p'

echo "== files containing DesktopControlCommand =="
rg -n "interface DesktopControlCommand|type DesktopControlCommand|export .*DesktopControlCommand|enum .*Desktop.*Action|case 'requestMainWindowClose'|closeMainWindowToTray" . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build /**' | sed -n '1,200p'

Repository: wecode-ai/Wegent

Length of output: 2824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== desktop-control-contract =="
cat -n -v wework/src/extensions/desktop-control-contract.ts | sed -n '1,220p'

echo "== automation lines 670-750 =="
cat -n -v wework/src/e2e/automation.ts | sed -n '670,750p'

echo "== automation lines 1068-1092 =="
cat -n -v wework/src/e2e/automation.ts | sed -n '1068,1092p'

Repository: wecode-ai/Wegent

Length of output: 5835


🌐 Web query:

Tauri v2 WebviewWindow getCurrentWindow close returns a promise documentation

💡 Result:

In Tauri v2, the close() method on a WebviewWindow instance returns a Promise<void> [1][2]. When you call getCurrentWebviewWindow().close(), it triggers a close-requested event, which allows the application to intercept the action if necessary [3][2]. Because it returns a promise, you can await the call if you need to ensure the action has been processed by the core process [1][2]. Key technical details: - Module: The getCurrentWebviewWindow function is exported from the @tauri-apps/api/webviewWindow module [4][5]. - Behavior: The close() method does not force the window to close immediately; it initiates a request that the window close [3][2]. If you need to force a window to close immediately, the documentation recommends using the destroy() method instead [3][2]. - Usage: It is commonly used in patterns where a specific window needs to close itself (e.g., a settings or dialog window) [5].

Citations:


Move getCurrentWindow().close() before posting the close result.

For both closeMainWindowToTray and requestMainWindowClose, postDesktopControlResult(..., ok: true) is already awaited before the actual close/tray request, so failures from those calls are reported after the desktop-control client has already resolved successfully. Issue the closeMainWindowToTray() / getCurrentWindow().close() calls before writing the success result, and let the surrounding result-handling flow report any failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/e2e/automation.ts` around lines 728 - 729, In the
closeMainWindowToTray and requestMainWindowClose handlers, invoke
closeMainWindowToTray() or getCurrentWindow().close() before awaiting
postDesktopControlResult with ok: true. Preserve the surrounding result-handling
flow so close/tray failures are reported instead of posting success first.

Source: Coding guidelines

case 'dispatchLocalModelSettingsChanged':
window.dispatchEvent(new CustomEvent(LOCAL_MODEL_SETTINGS_CHANGED_EVENT))
return ''
Expand Down Expand Up @@ -1077,6 +1079,8 @@ async function runDesktopControlClient(url: string): Promise<void> {
await postDesktopControlResult(url, { id: command.id, clientId, ok: true, value })
if (command.action === 'closeMainWindowToTray') {
await closeMainWindowToTray()
} else if (command.action === 'requestMainWindowClose') {
await getCurrentWindow().close()
}
} catch (error) {
await postDesktopControlResult(url, {
Expand Down
50 changes: 50 additions & 0 deletions wework/src/features/workbench/RuntimeTaskCloseGuard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { act, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { RuntimeTaskCloseGuard } from './RuntimeTaskCloseGuard'

const mocks = vi.hoisted(() => ({
closeRequestHandler: undefined as (() => void) | undefined,
installRuntimeTaskCloseGuard: vi.fn(),
closeMainWindowToTray: vi.fn(),
updateAppPreferences: vi.fn(),
unlisten: vi.fn(),
}))

vi.mock('@/hooks/useTranslation', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}))

vi.mock('@/lib/runtime-environment', () => ({
isTauriRuntime: () => true,
}))

vi.mock('@/tauri/appPreferences', () => ({
updateAppPreferences: mocks.updateAppPreferences,
}))

vi.mock('@/tauri/runtimeTaskCloseGuard', () => ({
closeMainWindowToTray: mocks.closeMainWindowToTray,
installRuntimeTaskCloseGuard: mocks.installRuntimeTaskCloseGuard,
}))

describe('RuntimeTaskCloseGuard', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.closeRequestHandler = undefined
mocks.installRuntimeTaskCloseGuard.mockImplementation(async handler => {
mocks.closeRequestHandler = handler
return mocks.unlisten
})
})

test('shows the close-to-tray prompt for every intercepted first close request', async () => {
render(<RuntimeTaskCloseGuard />)

await waitFor(() => expect(mocks.closeRequestHandler).toBeDefined())
act(() => {
mocks.closeRequestHandler?.()
})

expect(screen.getByTestId('runtime-task-close-confirm-overlay')).toBeInTheDocument()
})
})
5 changes: 1 addition & 4 deletions wework/src/features/workbench/RuntimeTaskCloseGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import { useTranslation } from '@/hooks/useTranslation'
import { isTauriRuntime } from '@/lib/runtime-environment'
import { updateAppPreferences } from '@/tauri/appPreferences'
import { closeMainWindowToTray, installRuntimeTaskCloseGuard } from '@/tauri/runtimeTaskCloseGuard'
import { useRuntimeTaskLifecycleStoreSnapshot } from './runtimeTaskLifecycle'

export function RuntimeTaskCloseGuard() {
const { t } = useTranslation('common')
const lifecycle = useRuntimeTaskLifecycleStoreSnapshot()
const [closeDialogOpen, setCloseDialogOpen] = useState(false)
const [closing, setClosing] = useState(false)

Expand All @@ -21,7 +19,6 @@ export function RuntimeTaskCloseGuard() {
let cancelled = false

void installRuntimeTaskCloseGuard(() => {
if (lifecycle.runningTaskKeys.size === 0) return
setClosing(false)
setCloseDialogOpen(true)
})
Expand All @@ -40,7 +37,7 @@ export function RuntimeTaskCloseGuard() {
cancelled = true
unlisten?.()
}
}, [lifecycle.runningTaskKeys.size])
}, [])

return (
<RuntimeTaskCloseConfirmDialog
Expand Down
Loading