diff --git a/desktop/src/components/workbench/WorkbenchPanel.test.tsx b/desktop/src/components/workbench/WorkbenchPanel.test.tsx index 9f92a44e..87699dbd 100644 --- a/desktop/src/components/workbench/WorkbenchPanel.test.tsx +++ b/desktop/src/components/workbench/WorkbenchPanel.test.tsx @@ -71,6 +71,18 @@ describe('WorkbenchPanel', () => { expect(screen.getByRole('tab', { name: 'Files' })).toHaveAttribute('aria-selected', 'false') }) + it('exposes a single compact workbench navigation landmark', () => { + render() + + const navigation = screen.getByTestId('workbench-navigation') + expect(navigation).toHaveAttribute('aria-label', 'Workbench navigation') + expect(navigation).toContainElement(screen.getByRole('button', { name: 'Back to conversation' })) + expect(navigation).toContainElement(screen.getByRole('tablist', { name: 'Workbench mode' })) + expect(navigation).toContainElement(screen.getByRole('button', { name: 'Close' })) + expect(navigation.className).toContain('h-12') + expect(screen.getByRole('tablist', { name: 'Workbench mode' }).className).not.toContain('border') + }) + it('switching to the browser tab calls setMode("browser")', () => { render() expect(useWorkspacePanelStore.getState().getMode(SESSION_ID)).toBe('workspace') diff --git a/desktop/src/components/workbench/WorkbenchPanel.tsx b/desktop/src/components/workbench/WorkbenchPanel.tsx index b57201cd..a188446b 100644 --- a/desktop/src/components/workbench/WorkbenchPanel.tsx +++ b/desktop/src/components/workbench/WorkbenchPanel.tsx @@ -72,12 +72,16 @@ export function WorkbenchPanel({ sessionId, variant = 'panel', onClose }: Workbe return (
-
+
{isTabVariant && ( @@ -126,12 +130,12 @@ export function WorkbenchPanel({ sessionId, variant = 'panel', onClose }: Workbe type="button" aria-label={t('workbench.close')} onClick={handleClose} - className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-[7px] text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)]/35" + className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-[7px] text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-info)]/30" >
-
+
{mode === 'browser' ? ( diff --git a/desktop/src/components/workspace/WorkspaceDiffSurface.test.tsx b/desktop/src/components/workspace/WorkspaceDiffSurface.test.tsx index 8a12c313..ca2cda8a 100644 --- a/desktop/src/components/workspace/WorkspaceDiffSurface.test.tsx +++ b/desktop/src/components/workspace/WorkspaceDiffSurface.test.tsx @@ -88,6 +88,53 @@ describe('WorkspaceDiffSurface', () => { expect(getCodeRow('const c = 4')).toHaveAttribute('data-selected', 'true') }) + it('exposes the selected Shift range on the diff rows and review rail', () => { + render() + + fireEvent.click(screen.getByRole('button', { name: 'Comment on src/a.ts new line 10' })) + fireEvent.click(screen.getByRole('button', { name: 'Comment on src/a.ts new line 12' }), { shiftKey: true }) + + const firstRow = getCodeRow('const a = 1').closest('[data-diff-row-id]') + const middleRow = getCodeRow('const b = 3').closest('[data-diff-row-id]') + const lastRow = getCodeRow('const c = 4').closest('[data-diff-row-id]') + expect(firstRow).toHaveAttribute('aria-selected', 'true') + expect(firstRow).toHaveAttribute('data-range-edge', 'start') + expect(middleRow).toHaveAttribute('aria-selected', 'true') + expect(middleRow).not.toHaveAttribute('data-range-edge') + expect(lastRow).toHaveAttribute('aria-selected', 'true') + expect(lastRow).toHaveAttribute('data-range-edge', 'end') + expect(document.querySelectorAll('[data-diff-selection-rail]')).toHaveLength(3) + expect(firstRow?.className).toContain('bg-[var(--color-info-container)]') + expect(firstRow?.className).not.toContain('bg-[var(--color-diff-added-bg)]') + expect(screen.getByTestId('workspace-code').className).toContain('text-[13px]') + const editor = screen.getByRole('textbox', { name: 'Review comment' }) + expect(editor.closest('[data-diff-editor]')?.className).toContain('rounded-[10px]') + expect(editor.className).toContain('var(--color-info)') + expect(screen.getByRole('button', { name: 'Comment on src/a.ts new line 10' })).not.toHaveAttribute('data-selection-focus') + const focusedGutter = screen.getByRole('button', { name: 'Comment on src/a.ts new line 12' }) + expect(focusedGutter).toHaveAttribute('data-selection-focus', 'true') + expect(focusedGutter.className).toContain('text-[var(--color-surface)]') + expect(focusedGutter.className).not.toContain('text-[var(--color-text-tertiary)]') + expect(focusedGutter.className).not.toContain('text-white') + const submit = screen.getByRole('button', { name: 'Submit review comment' }) + expect(submit.className).toContain('text-[var(--color-surface)]') + expect(submit.className).not.toContain('text-white') + }) + + it('cancels an inline review from the visible editor action', () => { + render() + const anchor = screen.getByRole('button', { name: 'Comment on src/a.ts new line 11' }) + + fireEvent.click(anchor) + fireEvent.change(screen.getByRole('textbox', { name: 'Review comment' }), { + target: { value: 'This draft should close' }, + }) + fireEvent.click(screen.getByRole('button', { name: 'Cancel' })) + + expect(screen.queryByRole('textbox', { name: 'Review comment' })).not.toBeInTheDocument() + expect(anchor).toHaveFocus() + }) + it('does not submit an empty review comment', () => { const onAddComment = vi.fn() render() diff --git a/desktop/src/components/workspace/WorkspaceDiffSurface.tsx b/desktop/src/components/workspace/WorkspaceDiffSurface.tsx index 808e0d66..ab353891 100644 --- a/desktop/src/components/workspace/WorkspaceDiffSurface.tsx +++ b/desktop/src/components/workspace/WorkspaceDiffSurface.tsx @@ -8,7 +8,7 @@ import { type KeyboardEvent, type MouseEvent, } from 'react' -import { CornerDownLeft, MessageSquare, Plus } from 'lucide-react' +import { CornerDownLeft, FileCode2, MessageSquare, Plus } from 'lucide-react' import { Highlight, type PrismTheme } from 'prism-react-renderer' import { useTranslation } from '../../i18n' import { @@ -266,7 +266,8 @@ export function WorkspaceDiffSurface({ } const handleRowClick = (event: MouseEvent, row: WorkspaceDiffRow) => { - activateRow(row, event.shiftKey, true) + if (event.shiftKey) event.currentTarget.focus() + activateRow(row, event.shiftKey, !event.shiftKey) } const moveRovingFocus = (row: WorkspaceDiffRow, direction: -1 | 1, extend: boolean) => { @@ -401,35 +402,45 @@ export function WorkspaceDiffSurface({ const renderEditor = () => review.selection && (
{status && (
{t(`workspace.diffReview.${status}`)}
)} -
-