Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
44 changes: 44 additions & 0 deletions design-qa.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,50 @@

final result: passed

---

# Wework 工作区标签设计 QA

## 目标

- 参考图:`/Users/axb-mac/.wegent-executor/workspace/attachments/draft/1785512913569/image.png`
- 实现范围:Wework 顶层任务 / 项目空间文档标签、项目名标题、关闭与恢复、新窗口。
- 验证环境:macOS 真实 Tauri 隔离实例,主窗口 1280×720;独立项目窗口 1280×800。

## 对照结果

- 对照图:`wework/test-results/ai-verify/final-workspace-tabs/10-reference-vs-implementation.png`
- 标题栏高度、文档标签宽度、圆角连接、关闭按钮、加号位置与参考方向一致。
- 标题栏使用稳定浅灰底色,选中标签使用内容表面色;未选中标签留在标题栏层,状态差异清晰。
- 顶层产品名统一为“项目空间”;具体项目打开后标签标题显示项目名,例如“产品规划”。
- 主窗口与独立项目窗口均无裁切、重叠、错误边框或圆角断裂。

## 核心流程截图

1. `01-task-active.png`:任务标签初始选中。
2. `02-new-tab-menu.png`:新建标签菜单。
3. `03-project-spaces-active.png`:项目空间顶层标签选中。
4. `04-project-name-active.png`:具体项目标题显示“产品规划”。
5. `05-switch-back-to-task.png`:切回任务,选中态同步。
6. `06-close-project-tab.png`:关闭项目标签后回退到任务。
7. `07-restore-closed-project-tab.png`:快捷键恢复已关闭项目标签。
8. `08-tab-context-menu.png`:标签上下文菜单。
9. `09-project-new-window.png`:项目在独立窗口中打开。

截图目录:`wework/test-results/ai-verify/final-workspace-tabs/`

## 缺陷审查

- P0:选中与未选中标签底色过于接近。已拆分标题栏和活动标签表面色,并重新截图验证。
- P0:新建项目后标签仍显示“项目空间”。已改为直接使用创建结果更新活动标签,真实流程与单测均通过。
- P1:右键菜单只能依赖鼠标。已增加 Shift+F10 / Context Menu 键入口。
- P1:动态项目窗口无法纳入隔离截图链。已增加工作区窗口 WebView 截图命令并验证。
- P2:未发现。

## 最终结论

final result: passed

## Dropdown alignment follow-up

- Source visual truth: `/Users/axb-mac/.wegent-executor/workspace/attachments/draft/1785311314363/image.png`
Expand Down
46 changes: 46 additions & 0 deletions wework/e2e/desktop/task-flow.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2911,6 +2911,49 @@ async function captureVerificationScreenshot(control, name, selector = 'body') {
return screenshotPath
}

async function verifyWorkspaceDocumentTabs(control) {
await control.command('waitFor', '[data-testid="workspace-tab-strip"]', {
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('waitFor', '[data-tab-kind="task"][aria-selected="true"]', {
text: '任务',
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})

await control.command('click', '[data-testid="workspace-tab-add"]')
await control.command('waitFor', '[data-testid="workspace-tab-add-menu"]', {
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('click', '[data-testid="workspace-tab-add-board"]')
await control.command('waitFor', '[data-tab-kind="board"][aria-selected="true"]', {
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command('waitFor', '[data-testid="cloud-todo-workspace"]', {
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await captureVerificationScreenshot(control, 'workspace-tabs-01-project-spaces-active.png')

await control.command(
'click',
'[data-tab-kind="task"] [data-testid^="workspace-tab-select-"]'
)
await control.command('waitFor', '[data-tab-kind="task"][aria-selected="true"]', {
text: '任务',
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,
})
await control.command(
'click',
'[data-tab-kind="board"] [data-testid^="workspace-tab-close-"]'
)
const snapshot = JSON.parse(await control.command('snapshot', 'body'))
assert.equal(
snapshot.testIds.some(testId => testId.startsWith('workspace-tab-board-')),
false,
'Closing the project-space document tab did not remove it from the titlebar'
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
await captureVerificationScreenshot(control, 'workspace-tabs-02-task-restored.png')
}

async function verifyCloudWorkPage(control) {
await control.command('navigate', 'body', { value: '/cloud-work' })
await control.command('waitFor', '[data-testid="cloud-work-page"]', {
Expand Down Expand Up @@ -10544,6 +10587,9 @@ last_updated = "2026-07-30T00:00:00Z"`

if (shouldRunDesktopCheckpoint('core-task-flow')) {
if (!GUIDANCE_SCROLL_ONLY) {
phase = 'workspace-document-tabs'
await verifyWorkspaceDocumentTabs(control)

phase = 'automation-lifecycle'
await verifyAutomationLifecycle(control, workspacePath)

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|debug|click|click-at|click-then-macrotask|seed-local-project|reload|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]
pnpm --filter wework ai:verify <capture|capture-popout|capture-workspace|snapshot|debug|click|click-at|click-then-macrotask|seed-local-project|reload|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 @@ -401,6 +401,7 @@ async function main() {
const action = {
capture: 'capture',
'capture-popout': 'capturePopoutWindow',
'capture-workspace': 'captureWorkspaceWindow',
snapshot: 'snapshot',
debug: 'getWorkbenchDebugSnapshot',
click: 'click',
Expand Down Expand Up @@ -438,6 +439,7 @@ async function main() {
options.selector ??
(command === 'capture' ||
command === 'capture-popout' ||
command === 'capture-workspace' ||
command === 'snapshot' ||
command === 'debug' ||
command === 'click-at' ||
Expand Down Expand Up @@ -490,7 +492,7 @@ async function main() {
effectiveTimeoutMs
)
}
if (command === 'capture' || command === 'capture-popout') {
if (command === 'capture' || command === 'capture-popout' || command === 'capture-workspace') {
if (!options.output) throw new Error('--output is required')
const prefix = 'data:image/png;base64,'
if (!value.value?.startsWith(prefix)) throw new Error('Invalid screenshot payload')
Expand Down
2 changes: 1 addition & 1 deletion wework/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": ["main", "popout-window"],
"windows": ["main", "popout-window", "workspace-*"],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
"permissions": [
"core:default",
"core:window:allow-close",
Expand Down
20 changes: 20 additions & 0 deletions wework/src-tauri/src/desktop_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@ pub async fn capture_popout_webview(app: tauri::AppHandle) -> Result<String, Str
capture_webview_impl(app, "popout-window", false).await
}

#[tauri::command]
pub async fn capture_workspace_webview(app: tauri::AppHandle) -> Result<String, String> {
use tauri::Manager;

let label = app
.webview_windows()
.into_iter()
.find_map(|(label, window)| {
(label.starts_with("workspace-") && window.is_focused().unwrap_or(false))
.then_some(label)
})
.or_else(|| {
app.webview_windows()
.into_keys()
.find(|label| label.starts_with("workspace-"))
})
.ok_or_else(|| "No workspace window is available".to_string())?;
capture_webview_impl(app, &label, false).await
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#[cfg(target_os = "macos")]
pub(crate) async fn capture_embedded_webview_png(
webview: tauri::Webview<tauri::Wry>,
Expand Down
1 change: 1 addition & 0 deletions wework/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4527,6 +4527,7 @@ pub fn run() {
cloud_authorization_window::position_cloud_authorization_window,
desktop_capture::capture_main_webview,
desktop_capture::capture_popout_webview,
desktop_capture::capture_workspace_webview,
acknowledge_frontend_resume_probe,
register_frontend_recovery_bridge,
#[cfg(desktop)]
Expand Down
16 changes: 7 additions & 9 deletions wework/src/App.apps.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,22 +202,22 @@ describe('App center route', () => {
})
}

test('renders the app center with fixed titlebar tabs on the app route', async () => {
test('renders the app center in an Apps document tab', async () => {
window.history.pushState({}, '', '/apps')

render(<App />)

await waitForStartupScreenToClose()

await waitFor(() => expect(window.location.pathname).toBe('/apps'))
expect(screen.getByTestId('desktop-app-switcher')).toHaveTextContent('任务')
expect(screen.getByTestId('workspace-tab-strip')).toHaveTextContent('Apps')
expect(screen.getByRole('tab', { name: /Apps/ })).toHaveAttribute('aria-selected', 'true')
expect(screen.getByTestId('desktop-auxiliary-surface')).toHaveClass(
'app-view-surface',
'rounded-xl',
'border'
)
expect(screen.queryByTestId('chrome-tab-todo')).not.toBeInTheDocument()
expect(screen.queryByTestId('chrome-tab-apps')).not.toBeInTheDocument()
expect(screen.getByTestId('workspace-tab-add')).toBeInTheDocument()
expect(screen.getByTestId('apps-page')).toBeInTheDocument()
expect(screen.getByRole('heading', { name: '管理你的办公与编码应用' })).toBeInTheDocument()
expect(await screen.findByText('Executor 状态')).toBeInTheDocument()
Expand Down Expand Up @@ -339,18 +339,16 @@ describe('App center route', () => {
expect(sectionTabs).not.toHaveClass('xl:hidden')
})

test('uses the fixed global view switcher on the app center route', async () => {
test('uses the document tab strip on the app center route', async () => {
window.history.pushState({}, '', '/apps')

render(<App />)

await waitForStartupScreenToClose()
expect(await screen.findByText('Executor 状态')).toBeInTheDocument()

const weworkTab = screen.getByTestId('chrome-tab-wework')
expect(screen.queryByTestId('chrome-tab-todo')).not.toBeInTheDocument()
expect(screen.queryByTestId('chrome-tab-apps')).not.toBeInTheDocument()
expect(weworkTab).toBeInTheDocument()
expect(screen.getByRole('tab', { name: /Apps/ })).toHaveAttribute('aria-selected', 'true')
expect(screen.getByTestId('workspace-tab-add')).toBeInTheDocument()

expect(screen.getByTestId('apps-sidebar-nav')).toBeInTheDocument()
})
Expand Down
97 changes: 62 additions & 35 deletions wework/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type CSSProperties,
Expand Down Expand Up @@ -77,6 +78,8 @@ import { useExperimentalFeaturesState } from '@/features/experimental-features/u
import { AppPreferencesProvider } from '@/features/app-preferences/AppPreferencesProvider'
import { useAppPreferencesState } from '@/features/app-preferences/useAppPreferencesState'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { useTranslation } from '@/hooks/useTranslation'
import { WorkspaceTabsProvider } from '@/features/workspace-tabs/WorkspaceTabsContext'

const WORKBENCH_STARTUP_REVEAL_TIMEOUT_MS = 6000
const POPOUT_WINDOW_LABEL = 'popout-window'
Expand All @@ -97,15 +100,26 @@ function hasTauriIpc() {
}

function useCurrentPath() {
const [path, setPath] = useState(stripAppBasePath(window.location.pathname))
return useCurrentLocation().pathname
}

function useCurrentLocation() {
const [location, setLocation] = useState(() => ({
pathname: stripAppBasePath(window.location.pathname),
search: window.location.search,
}))

useEffect(() => {
const handlePopState = () => setPath(stripAppBasePath(window.location.pathname))
const handlePopState = () =>
setLocation({
pathname: stripAppBasePath(window.location.pathname),
search: window.location.search,
})
window.addEventListener('popstate', handlePopState)
return () => window.removeEventListener('popstate', handlePopState)
}, [])

return path
return location
}

interface AppRoutesProps {
Expand Down Expand Up @@ -288,21 +302,31 @@ function LanguagePreferenceInitializer() {
}

function AppShell() {
const path = useCurrentPath()
const { t } = useTranslation('common')
const { pathname: path, search } = useCurrentLocation()
const { user, isLoading } = useAuth()
const cloudConnection = useCloudConnection()
const initialCloudConnection = {
backendUrl: cloudConnection.backendUrl,
isConnected: cloudConnection.isConnected,
token: cloudConnection.token,
}
const { activeAppKey, tabs, navigateToApp } = useChromeTabs(path)
const { activeAppKey, navigateToApp } = useChromeTabs(path)
const isTauri = isTauriRuntime()
const isPopoutWindow = isPopoutWindowRuntime()
const usesDesktopVibrancy = isTauri && !isPopoutWindow && getPlatform() === 'mac'
const titlebarOverlaysContent = false
const showChromeTitlebar = isTauri && !isPopoutWindow
const titlebarActiveKey = path === '/todo' ? 'todo' : activeAppKey
const workspaceTabStorageScope = isTauri ? getCurrentWindow().label : 'browser'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
const workspaceTabLabels = useMemo(
() => ({
task: t('workbench.workspace_tab_task', '任务'),
board: t('workbench.workspace_tab_board', '项目空间'),
agent: t('workbench.workspace_tab_agent', '智能体'),
auxiliary: t('workbench.workspace_tab_auxiliary', '工作区'),
}),
[t]
)
const [workbenchStartupReady, setWorkbenchStartupReady] = useState(false)
const [workbenchStartupRevealTimedOut, setWorkbenchStartupRevealTimedOut] = useState(false)
const openWeworkForAppshot = useCallback(() => {
Expand Down Expand Up @@ -496,41 +520,44 @@ function AppShell() {
}

const shell = (
<div
className={cn(
'h-dvh',
isPopoutWindow
? 'overflow-visible bg-transparent'
: usesDesktopVibrancy
? 'overflow-hidden bg-transparent'
: 'overflow-hidden bg-surface',
titlebarOverlaysContent ? 'relative' : 'flex flex-col'
)}
<WorkspaceTabsProvider
pathname={path}
search={search}
storageScope={workspaceTabStorageScope}
labels={workspaceTabLabels}
>
{showChromeTitlebar && (
<ChromeTitlebar
tabs={tabs}
activeKey={titlebarActiveKey}
onNavigate={appKey => (appKey === 'todo' ? navigateTo('/todo') : navigateToApp(appKey))}
iconOnlyTabs={isTauri}
showWorkspacePortals={activeAppKey !== 'wework'}
showFeedback={activeAppKey !== 'wework'}
/>
)}
<div
className={cn(
'min-h-0',
isPopoutWindow ? 'overflow-visible' : 'overflow-hidden',
titlebarOverlaysContent ? 'h-full' : 'flex-1'
'h-dvh',
isPopoutWindow
? 'overflow-visible bg-transparent'
: usesDesktopVibrancy
? 'overflow-hidden bg-transparent'
: 'overflow-hidden bg-surface',
titlebarOverlaysContent ? 'relative' : 'flex flex-col'
)}
>
<AppRoutes
onWorkbenchStartupReadyChange={setWorkbenchStartupReady}
onOpenWeworkForAppshot={isTauri ? openWeworkForAppshot : undefined}
/>
{showChromeTitlebar && (
<ChromeTitlebar
showWorkspacePortals={activeAppKey !== 'wework'}
showFeedback={activeAppKey !== 'wework'}
/>
)}
<div
className={cn(
'min-h-0',
isPopoutWindow ? 'overflow-visible' : 'overflow-hidden',
titlebarOverlaysContent ? 'h-full' : 'flex-1'
)}
>
<AppRoutes
onWorkbenchStartupReadyChange={setWorkbenchStartupReady}
onOpenWeworkForAppshot={isTauri ? openWeworkForAppshot : undefined}
/>
</div>
{!isPopoutWindow && <WeworkDevInstanceBadge />}
</div>
{!isPopoutWindow && <WeworkDevInstanceBadge />}
</div>
</WorkspaceTabsProvider>
)

if (isPopoutWindow) {
Expand Down
Loading
Loading