fix(web): stabilize session list alignment and scrolling (#1196)

This commit is contained in:
Ananovo
2026-07-28 11:18:05 +08:00
committed by GitHub
parent 0e3b4394ff
commit adb9e2094e
4 changed files with 41 additions and 30 deletions
+12 -8
View File
@@ -679,7 +679,7 @@ function SessionListSearch(props: {
return (
<div className="px-2 pb-2">
<div className="relative min-w-0">
<div className="pointer-events-none absolute inset-y-0 left-2 flex items-center text-[var(--app-hint)]">
<div className="pointer-events-none absolute inset-y-0 left-2.5 flex items-center text-[var(--app-hint)]">
<SearchIcon className="h-3.5 w-3.5" />
</div>
<input
@@ -861,7 +861,7 @@ function SessionItem(props: {
<button
type="button"
{...longPressHandlers}
className={`session-list-item group/session-row flex w-full flex-col gap-1 px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--app-link)] select-none rounded-lg ${selected ? 'bg-[var(--app-secondary-bg)]' : ''}`}
className={`session-list-item group/session-row flex w-full flex-col gap-1 py-2 pl-2.5 pr-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--app-link)] select-none rounded-lg ${selected ? 'bg-[var(--app-secondary-bg)]' : ''}`}
style={{ WebkitTouchCallout: 'none' }}
aria-current={selected ? 'page' : undefined}
aria-describedby={describedBy}
@@ -1217,13 +1217,14 @@ export function SessionList(props: {
}, [allGroups])
return (
<div className="mx-auto w-full max-w-content flex flex-col">
<div className="flex min-h-0 w-full flex-1 flex-col">
<div className="session-list-scrollbar-offset mx-auto w-full max-w-content shrink-0">
{renderHeader ? (
<div className="flex items-center justify-end px-3 py-1">
<div className="flex items-center justify-end px-2 py-1">
<button
type="button"
onClick={props.onNewSession}
className="session-list-new-button p-1.5 rounded-full text-[var(--app-link)] transition-colors"
className="session-list-new-button flex h-9 w-9 items-center justify-center rounded-full text-[var(--app-link)] transition-colors"
title={t('sessions.new')}
>
<PlusIcon className="h-5 w-5" />
@@ -1277,8 +1278,10 @@ export function SessionList(props: {
onChange={setMachineFilter}
/>
) : null}
</div>
<div className="flex flex-col gap-1 px-2 pt-1 pb-2">
<div className="app-scroll-y session-list-scrollbar-left min-h-0 flex-1">
<div className="mx-auto flex w-full max-w-content flex-col gap-1 pl-1.5 pr-2 pt-1 pb-2">
{groups.map((group) => {
const isCollapsed = isGroupCollapsed(group)
const visibleGroupSessions = getVisibleGroupSessions(group)
@@ -1294,7 +1297,7 @@ export function SessionList(props: {
return (
<div key={group.key}>
<div
className="group/project sticky top-0 z-10 flex items-center gap-2 px-1 py-1.5 text-left rounded-lg transition-colors hover:bg-[var(--app-subtle-bg)] cursor-pointer min-w-0 w-full select-none"
className="group/project sticky top-0 z-10 flex items-center gap-2 py-1.5 pl-2 pr-2 text-left rounded-lg transition-colors hover:bg-[var(--app-subtle-bg)] cursor-pointer min-w-0 w-full select-none"
onClick={() => toggleGroup(group.key, isCollapsed)}
title={group.directory}
>
@@ -1347,7 +1350,7 @@ export function SessionList(props: {
? showMoreSessions(group)
: collapseSessionGroup(group)}
className={cn(
'mx-2 my-1 rounded-md px-2 py-1 text-center text-xs text-[var(--app-hint)] transition-colors hover:bg-[var(--app-subtle-bg)] hover:text-[var(--app-fg)]',
'ml-2.5 mr-2 my-1 rounded-md px-2 py-1 text-center text-xs text-[var(--app-hint)] transition-colors hover:bg-[var(--app-subtle-bg)] hover:text-[var(--app-fg)]',
hiddenSessionCount > 0 && 'border border-dashed border-[var(--app-border)]'
)}
>
@@ -1363,6 +1366,7 @@ export function SessionList(props: {
)
})}
</div>
</div>
</div>
)
}
+15 -8
View File
@@ -352,19 +352,26 @@ body {
}
}
/* Keep the session list scrollbar on the left at every viewport size. */
.session-list-scrollbar-left {
direction: rtl;
scrollbar-gutter: stable;
}
.session-list-scrollbar-left > * {
direction: ltr;
}
/* Match fixed sidebar controls to the space reserved for the left scrollbar. */
.session-list-scrollbar-offset {
padding-left: 8px;
}
/* Desktop sidebar: use custom width from CSS variable.
Kept in sync with the Tailwind `split` breakpoint (see tailwind.config.ts)
so the sidebar width/resize applies as soon as the split view appears on
compact tablets. */
@media (min-width: 920px) {
.desktop-scrollbar-left {
direction: rtl;
}
.desktop-scrollbar-left > * {
direction: ltr;
}
/* Apply resizable width to sidebar.
Cap it against the viewport so a wide persisted width (up to 600px, from
resizing on desktop) can't crush the detail pane on a compact split.
+10 -10
View File
@@ -146,18 +146,18 @@ export default {
'session.tooltip.scheduled.next': '下次 {when} · 另有 {more} 条',
'session.tooltip.moreCount': '另有 {count} 条',
'session.time.justNow': '刚刚',
'session.time.minutesAgo': '{n} 分钟前',
'session.time.hoursAgo': '{n} 小时前',
'session.time.daysAgo': '{n} 天前',
'session.time.inLessThanMinute': '不到 1 分钟',
'session.time.inMinutes': '{n} 分钟后',
'session.time.inHours': '{n} 小时后',
'session.time.inDays': '{n} 天后',
'session.time.minutesAgo': '{n}分钟前',
'session.time.hoursAgo': '{n}小时前',
'session.time.daysAgo': '{n}天前',
'session.time.inLessThanMinute': '不到1分钟',
'session.time.inMinutes': '{n}分钟后',
'session.time.inHours': '{n}小时后',
'session.time.inDays': '{n}天后',
'session.time.soon': '即将',
'session.time.importedFromCodex.justNow': '刚刚从codex客户端导入',
'session.time.importedFromCodex.minutesAgo': '{n} 分钟前从codex客户端导入',
'session.time.importedFromCodex.hoursAgo': '{n} 小时前从codex客户端导入',
'session.time.importedFromCodex.daysAgo': '{n} 天前从codex客户端导入',
'session.time.importedFromCodex.minutesAgo': '{n}分钟前从codex客户端导入',
'session.time.importedFromCodex.hoursAgo': '{n}小时前从codex客户端导入',
'session.time.importedFromCodex.daysAgo': '{n}天前从codex客户端导入',
'session.cursorMigration.banner.title': '正在升级 Cursor 会话',
'session.cursorMigration.banner.body': '正在将此旧版会话切换到更安全的 ACP 协议。历史较长的会话需要 15-20 秒;对话会自动恢复,已输入但未发送的草稿不会丢失。',
'session.cursorMigration.bannerAmbiguous.title': 'Cursor 会话升级需要人工处理',
+4 -4
View File
@@ -535,8 +535,8 @@ function SessionsPage() {
className={`${isSessionsIndex ? 'flex' : 'hidden split:flex'} w-full shrink-0 flex-col bg-[var(--app-bg)]`}
style={{ '--sidebar-w': `${sidebar.width}px` } as React.CSSProperties}
>
<div className="bg-[var(--app-bg)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-content flex items-center justify-end px-3 py-2">
<div className="session-list-scrollbar-offset shrink-0 bg-[var(--app-bg)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto flex w-full max-w-content items-center justify-end px-2 py-2">
<div className="flex items-center gap-2">
<button
type="button"
@@ -579,7 +579,7 @@ function SessionsPage() {
<button
type="button"
onClick={() => navigate({ to: '/sessions/new' })}
className="session-list-new-button p-1.5 rounded-full text-[var(--app-link)] transition-colors"
className="session-list-new-button flex h-9 w-9 items-center justify-center rounded-full text-[var(--app-link)] transition-colors"
title={t('sessions.new')}
>
<PlusIcon className="h-5 w-5" />
@@ -588,7 +588,7 @@ function SessionsPage() {
</div>
</div>
<div className="app-scroll-y flex-1 min-h-0 desktop-scrollbar-left">
<div className="flex min-h-0 flex-1 flex-col">
{error ? (
<div className="mx-auto w-full max-w-content px-3 py-2">
<div className="text-sm text-red-600">{error}</div>