diff --git a/packages/web-shell/client/e2e/web-shell.conversation-search.spec.ts b/packages/web-shell/client/e2e/web-shell.conversation-search.spec.ts index 71697ad3388..3839fcd494b 100644 --- a/packages/web-shell/client/e2e/web-shell.conversation-search.spec.ts +++ b/packages/web-shell/client/e2e/web-shell.conversation-search.spec.ts @@ -51,7 +51,7 @@ test('search locates persisted history outside the live window without changing for (const theme of ['light', 'dark']) { for (const language of ['en', 'zh-CN']) { - for (const width of [1440, 390]) { + for (const width of [1600, 1440, 390]) { test(`conversation search content and layout ${theme} ${language} ${width} @smoke`, async ({ page, baseURL, @@ -74,19 +74,20 @@ for (const theme of ['light', 'dark']) { exact: true, }); await expect(bottom).toBeVisible(); + await expect(button).toBeVisible(); const searchBox = await button.boundingBox(); const timeline = page.getByRole('navigation', { name: /Session timeline|会话时间线/, }); - await expect( - timeline.getByRole('button', { - name: /Search this conversation|搜索当前会话/, - exact: true, - }), - ).toBeVisible(); expect(searchBox!.width).toBeLessThanOrEqual(20); expect(searchBox!.height).toBe(24); - if (width === 1440) { + if (width === 1600) { + await expect( + timeline.getByRole('button', { + name: /Search this conversation|搜索当前会话/, + exact: true, + }), + ).toBeVisible(); const tick = await timeline .locator('li button span') .first() @@ -96,6 +97,8 @@ for (const theme of ['light', 'dark']) { searchBox!.x + searchBox!.width / 2 - (tick!.x + tick!.width / 2), ), ).toBeLessThan(1); + } else { + await expect(timeline).toBeHidden(); } await page.screenshot({ path: `/tmp/qwen-12231-entry-${theme}-${language}-${width}.png`, diff --git a/packages/web-shell/client/e2e/web-shell.history-viewport.spec.ts b/packages/web-shell/client/e2e/web-shell.history-viewport.spec.ts index def39f11af4..2059eeb222c 100644 --- a/packages/web-shell/client/e2e/web-shell.history-viewport.spec.ts +++ b/packages/web-shell/client/e2e/web-shell.history-viewport.spec.ts @@ -50,7 +50,7 @@ async function historyScenario( baseURL: string | undefined, pageRecords: number, ) { - await page.setViewportSize({ width: 1440, height: 900 }); + await page.setViewportSize({ width: 1600, height: 900 }); const count = pageRecords * 12; const sessionId = `history-navigation-${pageRecords}`; const live = Array.from({ length: 40 }, (_, index) => diff --git a/packages/web-shell/client/e2e/web-shell.transcript-composer-alignment.spec.ts b/packages/web-shell/client/e2e/web-shell.transcript-composer-alignment.spec.ts index 218491bbd9f..f65efd9c4d7 100644 --- a/packages/web-shell/client/e2e/web-shell.transcript-composer-alignment.spec.ts +++ b/packages/web-shell/client/e2e/web-shell.transcript-composer-alignment.spec.ts @@ -29,7 +29,7 @@ for (const navigation of [true, false]) { page, baseURL, }, testInfo) => { - await page.setViewportSize({ width: 1440, height: 900 }); + await page.setViewportSize({ width: 1600, height: 900 }); const scenario = createWebShellDaemonScenario({ events: Array.from({ length: 4 }, (_, index) => [ userTextEvent('What is the weather?', { id: index * 3 + 1 }), @@ -117,7 +117,9 @@ for (const navigation of [true, false]) { 'color(srgb 1 1 1 / 0.96)', ); await scrollToBottom.click(); - for (const width of [1440, 1300, 1261, 1260, 1259, 1000, 802, 700, 599]) { + for (const width of [ + 1600, 1440, 1405, 1404, 1403, 1300, 1000, 802, 700, 599, + ]) { await page.setViewportSize({ width, height: 900 }); await expectSameEdges(message, composer); const viewport = page.locator('[data-history-viewport]'); @@ -132,7 +134,7 @@ for (const navigation of [true, false]) { }) .toBeLessThanOrEqual(1); const viewportBox = await viewport.boundingBox(); - if (viewportBox && viewportBox.width >= 1000) { + if (viewportBox && viewportBox.width >= 1128) { await expect(rail).toBeVisible(); const railBox = await rail.boundingBox(); const messageBox = await message.boundingBox(); @@ -142,7 +144,7 @@ for (const navigation of [true, false]) { } } { - await page.setViewportSize({ width: 1440, height: 900 }); + await page.setViewportSize({ width: 1600, height: 900 }); const shell = page.locator('[data-web-shell-root]'); const sidebar = page.getByRole('complementary', { name: 'Workspace sidebar', @@ -155,17 +157,37 @@ for (const navigation of [true, false]) { '--chat-regular-content-width', `${width}px`, ); + element.style.setProperty('--chat-content-width', `${width}px`); + element.style.setProperty('--chat-shell-width', `${width + 40}px`); }, minWidth); for (const offset of [-1, 0, 1]) { await page.setViewportSize({ - width: minWidth + sidebarWidth + offset, + width: minWidth + 128 + sidebarWidth + offset, height: 900, }); if (offset < 0) await expect(rail).toBeHidden(); - else await expect(rail).toBeVisible(); + else { + await expect(rail).toBeVisible(); + const button = rail.locator('button').first(); + const tick = button.locator(':scope > span').first(); + await button.hover(); + await expect + .poll(async () => (await tick.boundingBox())?.width ?? 0) + .toBeGreaterThan(27); + await expect + .poll(async () => { + const tickBox = await tick.boundingBox(); + const messageBox = await message.boundingBox(); + return tickBox && messageBox + ? messageBox.x - tickBox.x - tickBox.width + : -Infinity; + }) + .toBeGreaterThanOrEqual(16); + await page.mouse.move(500, 10); + } } } - await page.setViewportSize({ width: 1000 + sidebarWidth, height: 900 }); + await page.setViewportSize({ width: 1128 + sidebarWidth, height: 900 }); await shell.evaluate((element) => { element.style.setProperty('--chat-regular-content-width', '1200px'); }); @@ -174,8 +196,17 @@ for (const navigation of [true, false]) { element.style.setProperty('--chat-regular-content-width', '1000px'); }); await expect(rail).toBeVisible(); + await shell.evaluate((element) => { + element.style.setProperty('--chat-content-width', '100%'); + }); + await expect(rail).toBeHidden(); + await shell.evaluate((element) => { + element.style.setProperty('--chat-content-width', '1000px'); + element.style.setProperty('--chat-shell-width', '1040px'); + }); + await expect(rail).toBeVisible(); } - for (const width of [1440, 1360]) { + for (const width of [1600, 1500]) { await page.setViewportSize({ width, height: 900 }); const button = rail.locator('button').first(); const tick = button.locator(':scope > span').first(); diff --git a/packages/web-shell/client/e2e/web-shell.turn-navigation-scroll-follow.spec.ts b/packages/web-shell/client/e2e/web-shell.turn-navigation-scroll-follow.spec.ts index 846e858d745..eb26191d0b6 100644 --- a/packages/web-shell/client/e2e/web-shell.turn-navigation-scroll-follow.spec.ts +++ b/packages/web-shell/client/e2e/web-shell.turn-navigation-scroll-follow.spec.ts @@ -82,7 +82,7 @@ test('global turn navigation follows transcript scrolling @smoke', async ({ page, baseURL, }) => { - await page.setViewportSize({ width: 1440, height: 900 }); + await page.setViewportSize({ width: 1600, height: 900 }); const live = Array.from({ length: TURNS * 2 }, (_, index) => recordEvent( index, @@ -178,7 +178,7 @@ test('global turn navigation follows transcript scrolling @smoke', async ({ await expect(rail).toBeHidden(); await scrollTranscriptTo(page, 0.5); await expect.poll(() => ariaOrdinal(page)).toBeNull(); - await page.setViewportSize({ width: 1440, height: 900 }); + await page.setViewportSize({ width: 1600, height: 900 }); await expect(rail).toBeVisible(); await expect.poll(() => ariaOrdinal(page)).not.toBeNull(); const ratio = await markerOffsetRatio(page); diff --git a/packages/web-shell/client/hooks/useChatNavigationVisible.ts b/packages/web-shell/client/hooks/useChatNavigationVisible.ts index 9e53568e67d..9c353d22b66 100644 --- a/packages/web-shell/client/hooks/useChatNavigationVisible.ts +++ b/packages/web-shell/client/hooks/useChatNavigationVisible.ts @@ -13,13 +13,16 @@ export function useChatNavigationVisible( } const container = element.closest('[data-history-viewport]') ?? element; const update = () => { + const style = getComputedStyle(element); const minWidth = Number.parseFloat( - getComputedStyle(element).getPropertyValue( - '--chat-regular-content-width', - ), + style.getPropertyValue('--chat-regular-content-width'), ) || 1000; - setVisible(container.getBoundingClientRect().width >= minWidth); + // Reserve 64px gutters so hover-expanded ticks stay clear of the text. + setVisible( + style.getPropertyValue('--chat-content-width').trim() !== '100%' && + container.getBoundingClientRect().width >= minWidth + 128, + ); }; update(); const resize =