Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions docs/en/wework/workbench.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Press `Command+J` to open or close the bottom workspace panel. Opening the panel

Select **+** in the bottom tab bar to choose **Terminal**, **IDE**, or **Desktop**, depending on the active device's capabilities. Terminal starts in the active project or the task's Git worktree. IDE opens in the system default browser. When available, Desktop opens in Wework's built-in browser.

## Expand the right workspace

With the right workspace open, select **Expand panel** in its title bar to let files, previews, or change reviews fill the main workspace while keeping the composer available at the bottom. The expanded state is saved per conversation.

You can still collapse the left sidebar while the workspace is expanded, leaving only the right workspace visible. Select **Restore panel** in the upper-right corner, or **Latest turn** above the composer, to return to the side-by-side conversation and workspace layout. Closing the right workspace or its last tab also exits the expanded state.

## Navigate long conversations

When a conversation is taller than the current viewport, turn markers appear along the left side of the message area. The navigation stays centered in the conversation viewport instead of scrolling with message content. Select a marker to jump to that turn, or hover over it to preview the user request and assistant response summary.
Expand Down
6 changes: 6 additions & 0 deletions docs/zh/wework/workbench.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ sidebar_position: 5

点击底部标签栏的 **+**,可以根据当前设备能力选择 **Terminal**、**IDE** 或 **桌面**。Terminal 从当前项目目录启动,Worktree 任务从对应工作树启动;IDE 在系统默认浏览器中打开;桌面能力可用时在 Wework 内置浏览器中展示。

## 展开右侧工作区

打开右侧工作区后,点击标题栏中的 **展开面板**,可以让文件、预览或审核内容占满主工作区,同时保留底部输入框,方便一边查看内容一边继续对话。展开状态会按对话保存。

在展开状态下仍可收起左侧栏,使窗口只显示右侧工作区。点击右上角的 **恢复面板**,或点击输入框上方的 **最近一条**,即可返回对话与右侧工作区并排显示的布局。关闭右侧工作区或关闭其最后一个标签时,展开状态也会自动结束。

## 查看执行过程

任务运行时,对话会显示正在使用的工具和当前进度。已完成的工具调用会折叠为摘要,可以展开查看命令或修改过的文件。
Expand Down
104 changes: 103 additions & 1 deletion wework/e2e/desktop/task-flow.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,17 @@ async function waitForTopMetrics(control, selector, description, timeoutMs = 3_0
)
}

async function waitForElementWidth(control, selector, predicate, description, timeoutMs = 1_500) {
const startedAt = Date.now()
let metrics
while (Date.now() - startedAt < timeoutMs) {
metrics = await getSingleElementMetrics(control, selector, description)
if (predicate(metrics.width)) return metrics
await new Promise(resolvePromise => setTimeout(resolvePromise, 50))
}
throw new Error(`${description} remained ${metrics.width}px wide after ${timeoutMs}ms`)
}

function distanceFromBottom(metrics) {
return Math.max(0, metrics.scrollHeight - metrics.clientHeight - metrics.scrollTop)
}
Expand Down Expand Up @@ -7463,7 +7474,14 @@ async function main() {
)
await control.command('waitFor', activeBrowserInputSelector, { timeoutMs: UI_TIMEOUT_MS })
await control.command('fill', activeBrowserInputSelector, { value: retainedBrowserUrl })
await control.command('submit', activeBrowserInputSelector)
await control.command('click', bottomPanelToggleSelector)
await waitForControlValue(
control,
activeBrowserInputSelector,
retainedBrowserUrl,
'The Wework built-in browser did not commit its URL before switching conversations'
)
const firstTaskBottomWorkspaceSnapshot = await waitForSnapshot(
control,
value => {
Expand Down Expand Up @@ -7528,7 +7546,91 @@ async function main() {
restoredWorkspaceSnapshot.testIds.includes('right-workspace-browser-tab'),
'The browser tab was not restored after switching conversations'
)
await captureVerificationScreenshot(control, 'workspace-resources-restored-after-switch.png')
await control.command('finishAnimations', 'body')
await captureVerificationScreenshot(control, 'workspace-panel-01-default-split.png')

const expandedPanelToggleSelector =
'[data-testid="toggle-right-workspace-panel-expanded-button"]'
const rightPanelShellSelector = `${ACTIVE_WORKBENCH_SELECTOR} [data-testid="right-workspace-panel-shell"]`
await control.command('click', expandedPanelToggleSelector)
await control.command(
'waitFor',
'[data-testid="restore-conversation-from-expanded-workspace-button"]',
{ timeoutMs: UI_TIMEOUT_MS }
)
await control.command('finishAnimations', 'body')
assert.equal(
await control.command('getInlineStyle', rightPanelShellSelector, { value: 'width' }),
'100%',
'The expanded right workspace panel did not occupy the full workbench width'
)
const expandedWorkspaceSnapshot = JSON.parse(
await control.command('snapshot', ACTIVE_WORKBENCH_SELECTOR)
)
assert.equal(
expandedWorkspaceSnapshot.testIds.includes('right-workspace-resize-handle'),
false,
'The right workspace resize handle remained visible while expanded'
)
assert.ok(
expandedWorkspaceSnapshot.testIds.includes('project-chat-composer'),
'The main composer disappeared while the right workspace panel was expanded'
)
await captureVerificationScreenshot(control, 'workspace-panel-02-expanded.png')

await control.command('click', '[data-testid="collapse-sidebar-button"]')
await control.command('waitFor', '[data-testid="expand-sidebar-button"]', {
timeoutMs: UI_TIMEOUT_MS,
})
await control.command('finishAnimations', 'body')
const collapsedSidebarMetrics = await waitForElementWidth(
control,
'[data-testid="desktop-sidebar"]',
width => width <= 1,
'The desktop sidebar'
)
assert.ok(
collapsedSidebarMetrics.width <= 1,
'The desktop sidebar remained visible after it was collapsed'
)
assert.equal(
await control.command('getInlineStyle', rightPanelShellSelector, { value: 'width' }),
'100%',
'The expanded right workspace panel changed width after the sidebar collapsed'
)
const sidebarHiddenPanelMetrics = await getSingleElementMetrics(
control,
rightPanelShellSelector,
'The expanded right workspace panel'
)
assert.ok(
sidebarHiddenPanelMetrics.left <= 1,
`The expanded right workspace panel remained ${sidebarHiddenPanelMetrics.left}px from the left edge`
)
await control.command('pointerLeave', '[data-testid="desktop-sidebar-hover-edge"]')
await new Promise(resolvePromise => setTimeout(resolvePromise, 350))
await captureVerificationScreenshot(control, 'workspace-panel-03-expanded-sidebar-hidden.png')

await control.command('click', '[data-testid="expand-sidebar-button"]')
await control.command('waitFor', '[data-testid="collapse-sidebar-button"]', {
timeoutMs: UI_TIMEOUT_MS,
})
await control.command('finishAnimations', 'body')
await waitForElementWidth(
control,
'[data-testid="desktop-sidebar"]',
width => width >= 239,
'The desktop sidebar'
)
await control.command(
'click',
'[data-testid="restore-conversation-from-expanded-workspace-button"]'
)
await control.command('waitFor', '[data-testid="right-workspace-resize-handle"]', {
timeoutMs: UI_TIMEOUT_MS,
})
await control.command('finishAnimations', 'body')
await captureVerificationScreenshot(control, 'workspace-panel-04-restored-split.png')
await control.command('click', bottomWorkspaceTabCloseSelector)
await control.command('click', rightBrowserTabCloseSelector)

Expand Down
6 changes: 5 additions & 1 deletion wework/src/components/chat/ScrollableMessageArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface ScrollableMessageAreaProps {
turnNavigation?: RuntimeTurnNavigationItem[]
className?: string
scrollerClassName?: string
contentClassName?: string
messageListClassName?: string
stickyFooter?: ReactNode
stickyFooterClassName?: string
Expand Down Expand Up @@ -116,6 +117,7 @@ function areScrollableMessageAreaPropsEqual(
previous.turnNavigation !== next.turnNavigation ? 'turnNavigation' : null,
previous.className !== next.className ? 'className' : null,
previous.scrollerClassName !== next.scrollerClassName ? 'scrollerClassName' : null,
previous.contentClassName !== next.contentClassName ? 'contentClassName' : null,
previous.messageListClassName !== next.messageListClassName ? 'messageListClassName' : null,
previous.stickyFooter !== next.stickyFooter ? 'stickyFooter' : null,
previous.stickyFooterClassName !== next.stickyFooterClassName ? 'stickyFooterClassName' : null,
Expand Down Expand Up @@ -188,6 +190,7 @@ function ScrollableMessagePaneContent({
turnNavigation,
className,
scrollerClassName,
contentClassName,
messageListClassName,
stickyFooter,
stickyFooterClassName,
Expand Down Expand Up @@ -806,7 +809,8 @@ function ScrollableMessagePaneContent({
'min-w-0',
stickyFooter && 'flex-1 shrink-0',
(turnNavigationLoading || turnNavigationTargetMessageId || autoScrollSuspended) &&
'[overflow-anchor:none]'
'[overflow-anchor:none]',
contentClassName
)}
>
{messages.length === 0 ? (
Expand Down
52 changes: 51 additions & 1 deletion wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,8 @@ describe('DesktopWorkbenchLayout', () => {
function renderWorkspacePanelLayout({
mainWidth,
withAppearance = false,
}: { mainWidth?: number; withAppearance?: boolean } = {}) {
messages,
}: { mainWidth?: number; withAppearance?: boolean; messages?: WorkbenchMessage[] } = {}) {
if (mainWidth) {
mockDesktopWorkbenchMainWidth(mainWidth)
}
Expand All @@ -1215,6 +1216,7 @@ describe('DesktopWorkbenchLayout', () => {
const layout = (
<DesktopWorkbenchLayout
{...baseProps}
messages={messages}
state={{
...baseProps.state,
...workspacePanelState,
Expand Down Expand Up @@ -4103,6 +4105,54 @@ describe('DesktopWorkbenchLayout', () => {
expect(screen.getByTestId('workspace-file-tree')).toHaveClass('w-[240px]')
})

test('expands the right workspace panel, keeps the composer available, and restores chat', async () => {
renderWorkspacePanelLayout({
mainWidth: 1000,
messages: [
{
id: 'message-1',
role: 'assistant',
content: 'Ready',
status: 'done',
createdAt: '2026-07-25T00:00:00.000Z',
},
],
})

await userEvent.click(screen.getByTestId('toggle-right-workspace-panel-button'))
const content = screen.getByTestId('desktop-workbench-content')
const panelShell = screen.getByTestId('right-workspace-panel-shell')
const expandButton = screen.getByTestId('toggle-right-workspace-panel-expanded-button')

expect(expandButton).toHaveAttribute('aria-pressed', 'false')
await userEvent.click(expandButton)

expect(content).toHaveStyle({ width: '100%' })
expect(panelShell).toHaveStyle({ width: '100%' })
expect(panelShell).toHaveClass('absolute', 'inset-y-0', 'right-0')
expect(screen.queryByTestId('right-workspace-resize-handle')).not.toBeInTheDocument()
expect(screen.getByTestId('desktop-chat-scroll-sticky-footer')).toHaveClass('z-critical')
expect(screen.getByTestId('desktop-floating-composer-layer')).toHaveClass('z-critical')
expect(screen.getByTestId('project-chat-composer')).toBeInTheDocument()
expect(screen.getByTestId('toggle-right-workspace-panel-expanded-button')).toHaveAttribute(
'aria-pressed',
'true'
)

await userEvent.click(screen.getByTestId('collapse-sidebar-button'))

expect(screen.getByTestId('desktop-sidebar')).toHaveStyle({ width: '0px' })
expect(panelShell).toHaveStyle({ width: '100%' })

await userEvent.click(screen.getByTestId('restore-conversation-from-expanded-workspace-button'))

await waitFor(() => {
expect(content).toHaveStyle({ width: '420px' })
expect(panelShell).toHaveStyle({ width: 'calc(100% - 420px)' })
})
expect(screen.getByTestId('right-workspace-resize-handle')).toBeInTheDocument()
})

test('shows the workbench background through the right and bottom panels', async () => {
localStorage.setItem(
'wework.appearance',
Expand Down
Loading
Loading