fix(web): sidebar list expansion made the whole page scroll (#53)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-24 00:00:52 +08:00
committed by GitHub
parent b18ae8f5f1
commit 33e5f0bcd1
3 changed files with 27 additions and 3 deletions
+17
View File
@@ -5,6 +5,12 @@
* page, after which all 21 rows are visible and the "更多" row disappears (no more hidden
* rows, no more server pages).
*
* A list taller than the viewport must scroll INSIDE the sidebar: the document itself
* stays unscrollable before and after "更多". Each row's sr-only Agent name is
* position:absolute, and without a positioned scroller those boxes anchored to the
* initial containing block, stretched the document, and let the whole page scroll (the
* composer could be pushed up, leaving blank space below).
*
* Standalone spec: shares one server with the other specs, so it registers its own user
* (auto-provisions a default Project) and seeds sessions via the API.
*/
@@ -59,8 +65,19 @@ test("sidebar shows 20 sessions plus a More row; More loads the 21st and then di
const more = sidebar.getByRole("button", { name: "更多" });
await expect(more).toBeVisible();
// The 20-row list already exceeds the 720px viewport: it must scroll inside the
// sidebar, never stretch the document (the sr-only regression described up top).
const docScrollable = () =>
page.evaluate(
() => document.documentElement.scrollHeight > document.documentElement.clientHeight + 1,
);
expect(await docScrollable(), "document scrollable before 更多").toBe(false);
// More: raises the display cap and fetches the next server page → all 21 rows, no More left.
await more.click();
await expect(rows).toHaveCount(TOTAL);
await expect(more).toHaveCount(0);
// Still only the sidebar scrolls after the list grew past one page.
expect(await docScrollable(), "document scrollable after 更多").toBe(false);
});
+3 -1
View File
@@ -7,7 +7,9 @@ ROOT="$(cd "$HERE/../../.." && pwd)"
DATA="$(mktemp -d)"
MOCK_PORT="${MOCK_PORT:-8931}"
SRV_PORT="${SRV_PORT:-8930}"
export BASE_URL="http://127.0.0.1:$SRV_PORT"
# localhost, not 127.0.0.1: since the Workspace-preview split the server canonicalizes the
# App onto localhost and reserves 127.0.0.1 as the preview host, where /api answers 401.
export BASE_URL="http://localhost:$SRV_PORT"
export MOCK_URL="http://127.0.0.1:$MOCK_PORT"
cleanup() {
@@ -704,8 +704,13 @@ export function Sidebar({
))}
</nav>
{/* Session area (scrollable): grouped by Workspace (default) or by Agent */}
<div className="mt-3 min-h-0 flex-1 overflow-y-auto border-t border-gray-200 px-2 pb-2 dark:border-gray-800">
{/* Session area (scrollable): grouped by Workspace (default) or by Agent.
relative: the scroller acts as its own containing block, so absolute descendants
(each row's sr-only Agent name) anchor and scroll inside it — anchored to the
initial containing block instead, rows past the fold would bypass this
overflow-y-auto and stretch the **document**, so expanding "More" / a source
folder made the whole page scroll (composer pushed up, blank space below). */}
<div className="relative mt-3 min-h-0 flex-1 overflow-y-auto border-t border-gray-200 px-2 pb-2 dark:border-gray-800">
{/* Section header: list label + grouping-mode toggle (the choice persists in localStorage) */}
<div className="flex items-center justify-between px-1 pt-2">
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">