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
9 changes: 9 additions & 0 deletions apps/desktop/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ async function withE2eWindow(

export const test = base.extend<{
window: Page;
onboardingWindow: Page;
gitReviewWindow: { page: Page; projectRoot: string };
invocableSkillsWindow: Page;
linkColorWindow: Page;
Expand All @@ -350,6 +351,14 @@ export const test = base.extend<{
window: async ({}, use) => {
await withE2eWindow({ seed: true, readinessSelector: COMPOSER_INPUT, locale: 'zh' }, use);
},
onboardingWindow: async ({}, use) => {
await withE2eWindow({
seed: false,
readinessSelector: '[data-maka-contract="onboarding-card"]',
locale: 'zh',
showWindow: true,
}, use);
},
gitReviewWindow: async ({}, use) => {
await withE2eWindow(
{
Expand Down
98 changes: 98 additions & 0 deletions apps/desktop/e2e/onboarding-viewport.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { expect, test } from './fixtures';

test('first-run onboarding stays within the chat viewport', async ({ onboardingWindow }) => {
const geometry = await onboardingWindow.evaluate(() => {
const pageRoot = document.scrollingElement;
const scrollContainer = document.querySelector<HTMLElement>('[data-chat-scroll-container="true"]');
const surface = document.querySelector<HTMLElement>('[data-maka-contract="onboarding-surface"]');
const card = document.querySelector<HTMLElement>('[data-maka-contract="onboarding-card"]');
if (!pageRoot || !scrollContainer || !surface || !card) {
throw new Error('Onboarding geometry is unavailable');
}

const scrollRect = scrollContainer.getBoundingClientRect();
const surfaceRect = surface.getBoundingClientRect();
const cardRect = card.getBoundingClientRect();
return {
pageClientHeight: pageRoot.clientHeight,
pageScrollHeight: pageRoot.scrollHeight,
clientHeight: scrollContainer.clientHeight,
scrollHeight: scrollContainer.scrollHeight,
surfaceTop: surfaceRect.top,
surfaceBottom: surfaceRect.bottom,
viewportTop: scrollRect.top,
viewportBottom: scrollRect.bottom,
cardTop: cardRect.top,
cardBottom: cardRect.bottom,
};
});

expect(geometry.pageScrollHeight, JSON.stringify(geometry, null, 2)).toBe(
geometry.pageClientHeight,
);
expect(geometry.scrollHeight, JSON.stringify(geometry, null, 2)).toBe(geometry.clientHeight);
expect(geometry.surfaceTop).toBeGreaterThanOrEqual(geometry.viewportTop);
expect(geometry.surfaceBottom).toBeLessThanOrEqual(geometry.viewportBottom);
expect(geometry.cardTop).toBeGreaterThanOrEqual(geometry.viewportTop);
expect(geometry.cardBottom).toBeLessThanOrEqual(geometry.viewportBottom);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

test('first-run onboarding remains reachable at the minimum window size', async ({
onboardingWindow,
}) => {
await onboardingWindow.setViewportSize({ width: 480, height: 320 });

const surface = onboardingWindow.locator('[data-maka-contract="onboarding-surface"]');
const initialScrollTop = await surface.evaluate((element) => element.scrollTop);
await surface.hover();
await onboardingWindow.mouse.wheel(0, 10_000);
await expect.poll(() => surface.evaluate((element) => element.scrollTop)).toBeGreaterThan(
initialScrollTop,
);

const geometry = await onboardingWindow.evaluate(() => {
const pageRoot = document.scrollingElement;
const scrollContainer = document.querySelector<HTMLElement>('[data-chat-scroll-container="true"]');
const surface = document.querySelector<HTMLElement>('[data-maka-contract="onboarding-surface"]');
const content = surface?.querySelector<HTMLElement>('.maka-onboarding-center');
if (!pageRoot || !scrollContainer || !surface || !content) {
throw new Error('Onboarding geometry is unavailable');
}

const scrollRect = scrollContainer.getBoundingClientRect();
const surfaceRect = surface.getBoundingClientRect();
const contentRect = content.getBoundingClientRect();

return {
windowInnerWidth: window.innerWidth,
windowInnerHeight: window.innerHeight,
pageClientHeight: pageRoot.clientHeight,
pageScrollHeight: pageRoot.scrollHeight,
viewportClientHeight: scrollContainer.clientHeight,
viewportScrollHeight: scrollContainer.scrollHeight,
viewportTop: scrollRect.top,
viewportBottom: scrollRect.bottom,
surfaceClientHeight: surface.clientHeight,
surfaceScrollHeight: surface.scrollHeight,
surfaceTop: surfaceRect.top,
surfaceBottom: surfaceRect.bottom,
finalScrollTop: surface.scrollTop,
contentBottom: contentRect.bottom,
};
});

expect(geometry.windowInnerWidth).toBe(480);
expect(geometry.windowInnerHeight).toBe(320);
expect(geometry.pageScrollHeight, JSON.stringify(geometry, null, 2)).toBe(
geometry.pageClientHeight,
);
expect(geometry.viewportScrollHeight, JSON.stringify(geometry, null, 2)).toBe(
geometry.viewportClientHeight,
);
expect(geometry.surfaceScrollHeight).toBeGreaterThan(geometry.surfaceClientHeight);
expect(initialScrollTop).toBe(0);
expect(geometry.finalScrollTop).toBeGreaterThan(initialScrollTop);
expect(geometry.surfaceTop).toBeGreaterThanOrEqual(geometry.viewportTop);
expect(geometry.surfaceBottom).toBeLessThanOrEqual(geometry.viewportBottom);
expect(geometry.contentBottom).toBeLessThanOrEqual(geometry.surfaceBottom);
});
24 changes: 23 additions & 1 deletion apps/desktop/src/renderer/styles/onboarding.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@
overscroll-behavior: contain;
}

/* The first-run screen is a viewport, not a transcript. ChatMessageList keeps
an empty flex spacer before its empty state so ordinary chat heroes settle
low in the message flow; here that spacer gives the full-height onboarding
surface only half of the available block size. The surface then overflows
ChatLayout and its self-scroll root exposes a phantom page scrollbar. */
.maka-chat-layout:has(.maka-onboarding-surface) {
overflow-y: hidden;
}

.maka-chat-layout:has(.maka-onboarding-surface) > :first-child {
flex: 1 1 auto;
padding-block-end: 0;
overflow: hidden;
}

.maka-chat-message-list:has(.maka-onboarding-surface) > div > [aria-hidden]:has(+ :last-child) {
display: none;
}

/* ChatLayout always paints its frosted composer dock, even when the Composer
itself is hidden. Onboarding owns the whole empty surface, so that dock has
no content to support and would blur the card actions behind it. Derive the
Expand Down Expand Up @@ -62,8 +81,11 @@
display: grid;
place-items: center;
width: 100%;
min-height: calc(100dvh - 84px);
height: 100%;
min-height: 0;
padding: var(--space-8) var(--space-6);
overflow-y: auto;
overscroll-behavior: contain;
}

@media (max-width: 560px) {
Expand Down
Loading