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
103 changes: 6 additions & 97 deletions apps/desktop/e2e/sidebar-geometry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,20 @@
*/

/*
* Locks the two invariants of the Astryx sidenav column that live only in
* hand-tuned CSS and were, before this file, verified by nothing (#3834):
*
* 1. Geometry — `.maka-sidenav-motion { height: 100% }` (shell-layout.css).
* Without a definite height the wrapper grows to its unclipped content and
* the footer leaves the window. `sidenav footer stays inside the window`
* is the lock shell-layout.css's comment already names by this path.
*
* 2. The resize handle — the two workaround rules that keep it grabbable:
* `top: var(--h-titlebar)` (shell-layout.css) and `transform: none
* !important` (sidebar.css, the Astryx hitAreaOffsetX fix). A regression
* in either leaves a column that looks right and simply cannot be dragged.
* `resize handle drag ...` grabs the handle at its vertical centre — the
* exact point the bug left ungrabbable — and asserts the width changes.
*
* Both run on `projectSidebarWindow`: the only fixture that boots the shell
* expanded (so the handle is mounted) with a populated, overflowing list (60
* sessions, so the footer has content to be pushed past). It opens with the
* search modal over an inert shell, so dismiss it first — the same first step
* sidebar-project-row.spec.ts takes.
* Locks `.maka-sidenav-motion { height: 100% }` in shell-layout.css. Without a
* definite height, the sidenav grows to its unclipped content and pushes the
* footer below the window. `projectSidebarWindow` supplies the overflowing
* session list needed to expose that regression.
*/

import { expect, test } from './fixtures';
import {
SESSION_LIST_EXPANDED_MAX_WIDTH,
SESSION_LIST_EXPANDED_MIN_WIDTH,
} from '../src/renderer/features/session-navigation/testing';
import type { Locator, Page } from '@playwright/test';
import type { Page } from '@playwright/test';

async function revealPopulatedSidebar(page: Page): Promise<Locator> {
async function revealPopulatedSidebar(page: Page): Promise<void> {
await page.keyboard.press('Escape');
await expect(page.locator('[data-maka-contract="search-modal"]')).not.toBeVisible();
const sidebar = page.getByRole('navigation', { name: '任务列表' });
await expect(sidebar).toBeVisible();
return sidebar;
}

async function wrapperWidth(wrapper: Locator): Promise<number> {
const box = await wrapper.boundingBox();
if (!box) throw new Error('sidenav wrapper (.maka-sidenav-motion) has no visible bounds');
return box.width;
}

test('sidenav footer stays inside the window under an overflowing list', async ({
Expand Down Expand Up @@ -98,67 +71,3 @@ test('sidenav footer stays inside the window under an overflowing list', async (
expect(wrapperBox!.y + wrapperBox!.height).toBeLessThanOrEqual(innerHeight + 1);
expect(footerBox!.y + footerBox!.height).toBeLessThanOrEqual(innerHeight + 1);
});

test('resize handle drag from its vertical centre changes the column width', async ({
projectSidebarWindow: page,
}) => {
await revealPopulatedSidebar(page);

const handle = page.getByTestId('astryx-sidenav-resize-handle');
await expect(handle).toBeVisible();
const wrapper = page.locator('.maka-sidenav-motion');

const initialWidth = await wrapperWidth(wrapper);
expect(initialWidth).toBeGreaterThanOrEqual(SESSION_LIST_EXPANDED_MIN_WIDTH);
expect(initialWidth).toBeLessThanOrEqual(SESSION_LIST_EXPANDED_MAX_WIDTH);

// Grab the vertical centre. The Astryx hitAreaOffsetX bug leaves only the top
// half grabbable, so the centre no-ops unless sidebar.css's `transform: none
// !important` is applied — this point is the regression probe for that rule.
// Let locator actionability resolve the handle's live centre before reading
// its endpoint; replaying a box captured before that wait can miss a moving
// 16px handle under load.
await handle.hover();
const handleBox = await handle.boundingBox();
if (!handleBox) throw new Error('resize handle has no visible bounds');
const grabX = handleBox.x + handleBox.width / 2;
const grabY = handleBox.y + handleBox.height / 2;

await page.mouse.down();

// Astryx flags the separator while a drag is in flight; shell-layout.css keys
// its transition-suppression (`:has([data-resizing])`) on it, so the width
// tracks the pointer live rather than easing behind it. Check the second the
// pointer goes down, so a missed grab fails directly instead of timing out on
// an unrelated width assertion.
await expect(handle).toHaveAttribute('data-resizing', /.*/);
await page.mouse.move(grabX + 80, grabY, { steps: 20 });
await expect.poll(() => wrapperWidth(wrapper)).toBeGreaterThan(initialWidth + 40);
await page.mouse.up();
await expect(handle).not.toHaveAttribute('data-resizing', /.*/);

const widenedWidth = await wrapperWidth(wrapper);
expect(widenedWidth).toBeGreaterThan(initialWidth + 40);
expect(widenedWidth).toBeLessThanOrEqual(SESSION_LIST_EXPANDED_MAX_WIDTH + 1);

// Drag the other way to prove the handle also narrows the column, grabbing
// the centre again at the handle's new right-edge position.
await handle.hover();
const widenedHandleBox = await handle.boundingBox();
if (!widenedHandleBox) throw new Error('resize handle lost its bounds after widening');
const shrinkX = widenedHandleBox.x + widenedHandleBox.width / 2;
const shrinkY = widenedHandleBox.y + widenedHandleBox.height / 2;

await page.mouse.down();
await expect(handle).toHaveAttribute('data-resizing', /.*/);
// Undo the 80px widening. Keeping the reverse drag symmetric means every
// width accepted by the precondition stays above the collapsible region.
await page.mouse.move(shrinkX - 80, shrinkY, { steps: 20 });
await expect.poll(() => wrapperWidth(wrapper)).toBeLessThan(widenedWidth - 40);
await page.mouse.up();
await expect(handle).not.toHaveAttribute('data-resizing', /.*/);

const narrowedWidth = await wrapperWidth(wrapper);
expect(narrowedWidth).toBeLessThan(widenedWidth - 40);
expect(narrowedWidth).toBeGreaterThanOrEqual(SESSION_LIST_EXPANDED_MIN_WIDTH);
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ export {
} from './model/session-selection.js';
export {
readSessionListViewMode,
SESSION_LIST_EXPANDED_MAX_WIDTH,
SESSION_LIST_EXPANDED_MIN_WIDTH,
writeSessionListViewMode,
} from './model/session-list-layout.js';
export { createSessionRailLayoutStore } from './model/session-rail-layout-store.js';
Expand Down
15 changes: 0 additions & 15 deletions apps/desktop/src/renderer/styles/chat-header.css
Original file line number Diff line number Diff line change
Expand Up @@ -201,21 +201,6 @@
overscroll-behavior: contain;
}

/*
* Compatibility with Astryx #2573 until its fixed ChatLayout ships in a
* stable package. Published 0.2.0 gives the message area min-height: 100%,
* then adds the in-flow dock below it, creating a dock-height phantom range.
* These are the upstream flex contracts and can be removed on that upgrade.
*/
.maka-chat-layout > :first-child {
min-height: 0;
flex: 1 0 auto;
}

.maka-chat-layout > :last-child {
flex-shrink: 0;
}

.maka-chat-shell {
position: relative;
min-height: 0;
Expand Down
12 changes: 0 additions & 12 deletions apps/desktop/src/renderer/styles/module-pages/module-shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,6 @@
background: var(--muted);
}

/* The ResizeHandle's wider hit area is absolutely positioned with a vendor
bug: it gets the pill's translateY(-50%) centering without the pill's
top:50%, so the full-height strip lands shifted half its height upward —
over the page header, where it swallows the toolbar's clicks (技能 / MCP
with the inspector open; 定时任务 has the same latent strip). Drop the
stray translate. The !important is load-bearing: the vendor injects the
transform unlayered with :not(#\#) escalation, which beats any layered
product rule. */
.maka-module-page .astryx-resize-handle > :not(.astryx-resize-handle-pill) {
transform: none !important;
}

/* The page's one control bar: module switch on the left, this page's view +
filters on the right. It is the last row of the Layout header.

Expand Down
6 changes: 1 addition & 5 deletions apps/desktop/src/renderer/styles/shell-layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,7 @@
Keyed on the sidenav handle's own testid, not on the class: every Astryx
ResizeHandle carries that class, and a page's own inspector handle is a
`position: relative` flex item, so the same `top` shoved its divider 36px
down the page — it started below the header's rule instead of meeting it.

e2e/sidebar-geometry.spec.ts drags this handle from its vertical centre and
asserts the column width changes, so this rule and sidebar.css's grab-zone
fix both fail loudly if the handle stops being grabbable. */
down the page — it started below the header's rule instead of meeting it. */
.maka-shell-astryx [data-testid="astryx-sidenav-resize-handle"] {
top: var(--h-titlebar);
}
Expand Down
19 changes: 0 additions & 19 deletions apps/desktop/src/renderer/styles/sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,6 @@
height: 100%;
}

/*
* Astryx 0.2.0 ResizeHandle: when pillPlacement is not "center", hitAreaOffsetX
* applies translateY(-50%) without a matching top: 50%. The grab zone then only
* covers the top half of the handle, so a pointer drag at the vertical center
* (where the pill sits) hits the handle root — which has no pointerdown — and
* no-ops. SideNav uses pillPlacement="end" (sub-pixel X bias) and mounts the
* handle as a sibling of <nav.maka-session-panel>, not a descendant — target the
* handle test id. Remove once Astryx fixes hitAreaOffsetX.
*
* e2e/sidebar-geometry.spec.ts drags the handle from its vertical centre — the
* half this fix restores — and asserts the column resizes, so removing this
* before Astryx is fixed fails loudly instead of silently killing the grab.
*/
[data-testid="astryx-sidenav-resize-handle"] > div:not(.astryx-resize-handle-pill) {
/* !important: Astryx StyleX injects the bad transform unlayered, which
otherwise beats every rule in @layer maka.legacy. */
transform: none !important;
}

.maka-session-list {
min-width: 0;
max-width: 100%;
Expand Down