From 63e00f1e5fe427dc571794e59d29263eb88154f3 Mon Sep 17 00:00:00 2001 From: Jaxton07 Date: Tue, 22 Sep 2026 10:31:29 +0800 Subject: [PATCH] feat(sidebar): polish project group interactions --- docs/INDEX.md | 4 +-- .../src/components/sidebar/SessionRow.tsx | 1 - .../src/components/sidebar/SidebarGroup.tsx | 32 +++++++++++++------ .../src/renderer/src/styles/globals.css | 21 +++++++++++- scripts/check-sidebar-group-scroll.mjs | 24 +++++++------- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/docs/INDEX.md b/docs/INDEX.md index 92148201..690e40c4 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -206,7 +206,7 @@ src/ | `composer/` | **Composer**(装配层 ~330 行,键盘事件分发)+ 三 hook:`use-composer-send`(ensureSession 懒创建/followUp 排队/停止先 clearQueue/发送失败草稿回填)、`use-slash-menu`(命令拉取+胶囊回填+导航)、`use-at-completion`(@ token 探测/续钻/胶囊弹回)+ 展示件 QueueBar/ImageTray/AttachmentChip/**QuoteChip**(选中引用胶囊)/SendErrorBar + **ModelPicker** / **ThinkingPicker**(按模型 thinkingLevels 过滤+clamp)/ **PermissionPicker**(会话权限模式 chip:默认/完全访问)/ **SlashMenu** / **AtMenu** / **ContextRing** + 纯函数 `slash-filter`/`at-files`/`send-error`/`quote`/`model-filter`(各有测试) | | `session/` | **SessionTabBar** + SessionTab/TabPill(**v8:顶栏胶囊 = 置顶会话(不看 tab 开没开),严格只含置顶**,顺序就是 `pinnedSessions`;未置顶会话只在左栏;空态一行 12px `ink-faint` 提示;状态收拢到头像图标;dnd-kit 拖拽排序(改的也是 `pinnedSessions`),DragOverlay ghost + 轴锁定;拖拽期间退出 drag-region;**最左 = 左栏开合按钮 `PanelLeftIcon`**;胶囊右键 → `ui/ContextMenu`「重命名 / 置顶」;叉叉 = 取消置顶 + 从顶栏清除)/ **RenamePopover**(重命名浮层:锚在胶囊/指针锚点,160ms 进场 / 120ms 退场 + Enter/点外提交/Esc 取消)/ **SessionAvatar** + `session-status`(头像底色语义 + 状态/标题纯逻辑,顶栏与左栏共用;`SessionAvatar` 已无引用,留作备用)/ `session-menu`(菜单可用性与动作:`sidebarMenuKind` 定形态(普通 / 无菜单)、只读会话不给菜单、置顶只动 `pinnedSessions`、重命名落盘(**同步 tabs 与历史列表两份拷贝**)+失败 toast、复制诊断、删除会话;顶栏胶囊与左栏会话行共用)/ **ProjectBranchPicker**(从 `projects/` 移入的项目/分支选择器 chip;**只在无 active(新会话页)显示**,真实会话的项目创建时已绑定不可改)/ **DockSlot**(底部交换槽仲裁:权限 > 扩展对话框 > Composer)/ **ApprovalDock**(权限审批:async respond 成功才移除面板,失败保留重试)/ **InteractionDock**(扩展对话框四卡 select/input/editor/confirm:键盘 ↑↓/Enter/数字/Esc + 倒计时显示,裁决在 backend)/ **TrustDialog**(项目信任两选项)/ **UpdateButton**(顶栏更新按钮) | | `settings/` | **SettingsDialog**(PANELS 注册表;分类 = 静态 + 插件 settings.panel 贡献动态拼接)/ **GeneralPanel**(语言/上下文管理二态/channel-watch 开关)/ **AppearancePanel**(顶部 Tab 分栏:「基础」=主题三段/背景图/**显示顶栏开关**/中央动画,「UI 插件」= 原独立分类并入的 UiPluginsSection:总开关/插件卡/槽位指派,设计稿 .local/design/ux/appearance-ui-plugins)/ **SkillsPanel** / **McpPanel**(SDK 0.84 无 MCP,占位)/ **ExtensionsPanel** + `extensions/`(目录浏览/安装/卸载,subagent 包安装两段式确认)/ **UiPluginsSection**(挂在 AppearancePanel 的 UI 插件 Tab 下) / **LanObserverPanel** / **AboutPanel** / `providers/`(ProvidersPanel + ProviderRow 操作全图标化 / LoginDialog(交互登录对话框:OAuth + api_key 交互流)/ CustomProviderForm + ModelRowsEditor(逐模型行编辑器)/ BuiltinProviderEditForm(内置端点覆写)/ SubagentPanel(子代理执行器开关 + 逐代理模型/思考深度偏好)+ `model-rows` 纯函数) | -| `sidebar/` | **Sidebar**(常驻左栏容器:取 store 数据(历史 + 内存会话 + `activeCwd = store.cwd`)→ 调 `lib/sidebar-groups` 的 **`deriveSidebarNavigation`** 一条龙派生(含只读子会话过滤与项目表同源)→ 分发 props,240↔0 push 过渡 + 收起时 `inert`;主体是唯一外层滚动容器,带 `data-sidebar-scroll-root` 供 CDP 验收定位)/ **SidebarHeader**(滚动区外常驻的新会话入口 + 搜索框;新会话复用全局 draft)/ **SidebarGroup**(日常与各项目共用的分组骨架:分组行 + 可折叠的 `SidebarSessionList` + 空态,并把项目 cwd 接到定向 draft 入口;**标题行渲染在列表容器之外**,所以标题上的滚轮归外层导航)/ **SidebarSessionList**(会话列表单一实现:`SIDEBAR_SESSION_LIST_MAX_ROWS=8` × `SIDEBAR_SESSION_ROW_HEIGHT=31px` = 248px 上限 + `overflow-y-auto/overflow-x-hidden` + 隐藏滚动条;**仅在实际溢出(> 8 行)时挂 `overscroll-y-contain`** —— 给不溢出的滚动容器挂 contain 会吞掉 wheel、外层不滚,故 contain 与 `data-scrollable` 共用同一个布尔判据;**边缘淡出**:滚动位置写成 `data-fade-top/bottom`,`globals.css` 的 `.sidebar-session-list` 按这两个属性给 `mask-image` 渐变(alpha 遮罩、不引颜色,深浅主题共用;高度旋钮 = 变量 `--session-list-fade`),不溢出的列表两面都干净;淡出属性由 effect 独占、**不写进 JSX**(否则 React 重渲染会把滚动中的值冲回初值);纯 props 驱动,不读 store/不排序)/ **ProjectRow**(图标表达展开态:项目 = folder ↔ folder-open、日常 = 恒咖啡;行末 hover 出 Codex 式 «⋯» + 新会话按钮(日常仅新会话),菜单锚在**指针点**,另含置顶标记、移除项目二次确认)/ **SessionRow**(无头像文字 + 行尾状态点 + 右键菜单锚点;行上带 `data-session-id` / `data-session-active` 只读属性供 CDP 验收定位与判定,不影响视觉)/ **SessionMenu**(会话行右键三层状态机:菜单 → 改名气泡 → 删除确认;只读子会话不弹菜单)/ **ProjectSection**(「项目」小标:**v7 起固定标题不可折叠** + 常显「+」)/ **SidebarFooter**(设置;顶栏关闭时补「本轮改动」)/ **ProjectMenu**(项目 «⋯» 菜单项 builder)/ `useExpandedGroups`(展开态读写:起点由 `expandedGroupsTouched` 决定;写入时 store 同一次 set + 单个补丁置 touched) | +| `sidebar/` | **Sidebar**(常驻左栏容器:取 store 数据(历史 + 内存会话 + `activeCwd = store.cwd`)→ 调 `lib/sidebar-groups` 的 **`deriveSidebarNavigation`** 一条龙派生(含只读子会话过滤与项目表同源)→ 分发 props,240↔0 push 过渡 + 收起时 `inert`;主体是唯一外层滚动容器,带 `data-sidebar-scroll-root` 供 CDP 验收定位)/ **SidebarHeader**(滚动区外常驻的新会话入口 + 搜索框;新会话复用全局 draft)/ **SidebarGroup**(日常与各项目共用的分组骨架:分组行 + 可折叠的 `SidebarSessionList` + 空态,并把项目 cwd 接到定向 draft 入口;展开内容常驻 DOM,通过 `sidebar-group-content` 的 grid `0fr↔1fr` + opacity 做 180ms 开合动画,折叠态 `inert`/`aria-hidden`,动画结束后重置内层 scrollTop;**标题行渲染在列表容器之外**,所以标题上的滚轮归外层导航)/ **SidebarSessionList**(会话列表单一实现:`SIDEBAR_SESSION_LIST_MAX_ROWS=8` × `SIDEBAR_SESSION_ROW_HEIGHT=31px` = 248px 上限 + `overflow-y-auto/overflow-x-hidden` + 隐藏滚动条;**仅在实际溢出(> 8 行)时挂 `overscroll-y-contain`** —— 给不溢出的滚动容器挂 contain 会吞掉 wheel、外层不滚,故 contain 与 `data-scrollable` 共用同一个布尔判据;**边缘淡出**:滚动位置写成 `data-fade-top/bottom`,`globals.css` 的 `.sidebar-session-list` 按这两个属性给 `mask-image` 渐变(alpha 遮罩、不引颜色,深浅主题共用;高度旋钮 = 变量 `--session-list-fade`),不溢出的列表两面都干净;淡出属性由 effect 独占、**不写进 JSX**(否则 React 重渲染会把滚动中的值冲回初值);纯 props 驱动,不读 store/不排序)/ **ProjectRow**(图标表达展开态:项目 = folder ↔ folder-open、日常 = 恒咖啡;行末 hover 出 Codex 式 «⋯» + 新会话按钮(日常仅新会话),菜单锚在**指针点**,另含置顶标记、移除项目二次确认)/ **SessionRow**(无头像文字 + 行尾状态点 + 右键菜单锚点;行上带 `data-session-id` / `data-session-active` 只读属性供 CDP 验收定位与判定,不影响视觉)/ **SessionMenu**(会话行右键三层状态机:菜单 → 改名气泡 → 删除确认;只读子会话不弹菜单)/ **ProjectSection**(「项目」小标:**v7 起固定标题不可折叠** + 常显「+」)/ **SidebarFooter**(设置;顶栏关闭时补「本轮改动」)/ **ProjectMenu**(项目 «⋯» 菜单项 builder)/ `useExpandedGroups`(展开态读写:起点由 `expandedGroupsTouched` 决定;写入时 store 同一次 set + 单个补丁置 touched) | | `ui/` | Button(ghost/primary × sm/md × danger)/ Dropdown / Switch(统一受控开关,支持 indeterminate)/ **Tooltip**(自定义悬浮提示,新增提示一律用它不用原生 title;右缘元素传 `align="end"` 防幻影横向滚动条)/ **ContextMenu**(通用右键菜单:portal 到 body + `place-menu` 纯函数定位,点外/Esc/选中/滚动关闭;有边框卡片外观(`rounded-xl border border-border bg-surface shadow-pop`),项高 30px、支持 `danger` 红项与 `separatorBefore` 分隔线,不再有 veil 变体;会话胶囊 / 文件行 / 左栏会话行共用,文件行三项菜单见 **FilePathMenu**)/ `place-menu`(`placeMenu` 定位 + `anchorOfElement` 锚点转换,纯函数有测试;**左栏项目 «⋯» 与右键菜单一律传指针锚点** `{ left: clientX, top: clientY, width: 0, height: 0 }` —— 传元素矩形会把高度多算一遍,菜单掉到行下方)/ **ConfirmDialog**(破坏性操作二次确认:极淡蒙层 `bg-ink/6` + 392px 细边框卡片 + Portal 到 body 且 `z-[60]` 压过右键菜单;点卡片外/Esc/✕ = 取消,文案与按钮由调用方给;`autoFocusConfirm` 把默认焦点给确认按钮(不传则是键盘 Enter 不确认);非破坏性场景别默默加上)/ **QuitConfirmDialog**(Windows 退出前确认,文案走 i18n `quitConfirm.*`,并回 `app:quitDialogShown` 上屏回执) | | `icons/` | 内联 SVG 集中管理;`icons.test.ts` 校验所有 path `d` 语法(防残缺数据被 Chromium 丢弃) | @@ -234,7 +234,7 @@ src/ | 上下文用量圆环 | `composer/ContextRing.tsx` + `hooks/use-context-usage.ts`(事件驱动刷新,与插件 host API 共用)→ IPC getContextUsage → SDK `session.getContextUsage()`(percent null 或无消息不渲染;<60% 灰 / 60-85% 琥珀 / >85% 红) | | 每轮计时行 + 文件变更 chip + diff 侧栏 | 计时 shared `transcript/turn-timings.ts`(deriveTurnTimings;分量 = reducer 盖戳的 `UIToolCall.endedAt` + `runEndedAt` 定格 + 历史回放透传 toolResult timestamp);变更 shared `transcript/turn-files.ts`(deriveTurnChanges)+ `chat-rows.ts`(行定位:每轮必有计时行,lan-web 不传 timings 保持旧行为);渲染 `chat/TurnDiffChip.tsx`(timer 恒在首位)+ `diff/DiffSidebar.tsx`(含 BranchRow);开关 = SessionTabBar 的 DiffIcon 按钮 + `stores/ui.ts` diffSidebarOpen | | 顶栏 tab / 拖拽排序 / 置顶 | `session/SessionTabBar.tsx`(dnd-kit,DragOverlay ghost 拾起时实测宽度沿用原胶囊 + 轴锁定 + drag-region 退出)+ `stores/sessions.ts` `selectBarSessions`(**顶栏严格 = 置顶表**:会话被置顶就算 tab 没开也在顶栏,点击自动开;新会话不进顶栏)+ `stores/ui-preferences.ts` `reorderPinned`(拖拽改 `pinnedSessions` 顺序) | -| 左侧栏(分组 / 开合 / 置顶 / 搜索 / 移除项目 / 会话右键菜单 / 组内限高滚动) | 组件 `components/sidebar/*`(容器 → `Sidebar`;分组会话列表 → **`SidebarSessionList`**:8 行 × 31px = 248px 上限 + 原生嵌套滚动,`overscroll-y-contain` 只在溢出时挂,规则见 `docs/PITFALLS.md`「contain 吞 wheel」;纯派生 → `lib/sidebar-groups.ts`,有单测)+ 持久化字段 `barSessionsVisible` / `sidebarCollapsed` / `expandedGroups` + `expandedGroupsTouched` / `pinnedProjects`(`ui-preferences.ts` → main `ui-state.ts` normalize + 旧文件缺字段时的 touched 迁移)+ 宽度过渡与状态点呼吸 `styles/globals.css` 的 `.sidebar*` 段;置顶会话的行首图钉在 `sidebar/SessionRow.tsx`(行首 16×16 图标槽,标题 x 恒 38);验收/截图 `scripts/check-sidebar-group-scroll.mjs`(真实 wheel 滚动归属)、`scripts/shoot-sidebar.mjs`(逐帧) | +| 左侧栏(分组 / 开合 / 置顶 / 搜索 / 移除项目 / 会话右键菜单 / 组内限高滚动) | 组件 `components/sidebar/*`(容器 → `Sidebar`;分组会话列表 → **`SidebarSessionList`**:8 行 × 31px = 248px 上限 + 原生嵌套滚动,`overscroll-y-contain` 只在溢出时挂,规则见 `docs/PITFALLS.md`「contain 吞 wheel」;纯派生 → `lib/sidebar-groups.ts`,有单测)+ 持久化字段 `barSessionsVisible` / `sidebarCollapsed` / `expandedGroups` + `expandedGroupsTouched` / `pinnedProjects`(`ui-preferences.ts` → main `ui-state.ts` normalize + 旧文件缺字段时的 touched 迁移)+ 宽度过渡、分组开合动画与状态点呼吸在 `styles/globals.css` 的 `.sidebar*` 段(均覆盖 reduced-motion);置顶会话的行首图钉在 `sidebar/SessionRow.tsx`(行首 16×16 图标槽,标题 x 恒 38);验收/截图 `scripts/check-sidebar-group-scroll.mjs`(真实 wheel 滚动归属)、`scripts/shoot-sidebar.mjs`(逐帧) | | 顶栏内容 / 显隐 | 顶栏**常驻**(窗口拖动、左栏开合、变更侧栏入口都在上面,v9 起不再整条隐藏)→ `App.tsx` 无条件渲染 `SessionTabBar`;设置项「顶栏显示会话」(`settings/AppearancePanel.tsx` → `barSessionsVisible`)只控制**要不要出置顶会话胶囊**:关掉后顶栏不出胶囊、也不出空态提示,会话全在左栏(v9 已删旧版的三处兜底:左栏头 44px 拖拽带 + 「+」、聊天列顶 12px 隐形拖拽带、左栏底部「本轮改动」入口) | | 右侧变更浮层 | `components/diff/DiffSidebar.tsx` + `globals.css` 的 `.diff-sidebar`(固定 `min(420px, 38vw)`,transform 进退,不压缩聊天列);开关在顶栏(顶栏关闭时在左栏底部),关闭入口 = 开关 / 面板关闭按钮 / Esc | | 权限审批面板 | `backend/src/permissions/gate.ts`(队列)+ `session/ApprovalDock.tsx`(快捷键 Enter/A/D/Esc;await 成功才移除) | diff --git a/packages/desktop/src/renderer/src/components/sidebar/SessionRow.tsx b/packages/desktop/src/renderer/src/components/sidebar/SessionRow.tsx index f57edf2e..cee4865d 100644 --- a/packages/desktop/src/renderer/src/components/sidebar/SessionRow.tsx +++ b/packages/desktop/src/renderer/src/components/sidebar/SessionRow.tsx @@ -42,7 +42,6 @@ export function SessionRow({ // 可测试性只读属性(spec §6:CDP 按 sessionId 定位行,而不是靠标题文本匹配;纯属性,不影响视觉/交互) data-session-id={session.sessionId} data-session-active={active ? "true" : "false"} - title={title} className={`flex h-[31px] w-full items-center gap-2 rounded-[7px] px-1.5 text-left text-[13.5px] ${ active ? "bg-bubble font-medium text-ink" : "text-ink-2 hover:bg-hover hover:text-ink" }`} diff --git a/packages/desktop/src/renderer/src/components/sidebar/SidebarGroup.tsx b/packages/desktop/src/renderer/src/components/sidebar/SidebarGroup.tsx index 2822077a..3925a441 100644 --- a/packages/desktop/src/renderer/src/components/sidebar/SidebarGroup.tsx +++ b/packages/desktop/src/renderer/src/components/sidebar/SidebarGroup.tsx @@ -53,15 +53,29 @@ export function SidebarGroup({ onTogglePin={onTogglePin ? () => onTogglePin(group.cwd) : undefined} onRemove={onRemove} /> - {group.expanded && ( - void openSession(session)} - onContextMenu={(session, anchor) => sessionMenu.open(session, anchor)} - /> - )} +
{ + if (event.target !== event.currentTarget || event.propertyName !== "grid-template-rows") return; + if (!group.expanded) { + const list = event.currentTarget.querySelector("[data-sidebar-session-list]"); + if (list) list.scrollTop = 0; + } + }} + > +
+ void openSession(session)} + onContextMenu={(session, anchor) => sessionMenu.open(session, anchor)} + /> +
+
{sessionMenu.element} ); diff --git a/packages/desktop/src/renderer/src/styles/globals.css b/packages/desktop/src/renderer/src/styles/globals.css index 1c55b8ca..c34eb460 100644 --- a/packages/desktop/src/renderer/src/styles/globals.css +++ b/packages/desktop/src/renderer/src/styles/globals.css @@ -960,6 +960,24 @@ code { opacity: 0; transform: translateX(-28px); } +/* 分组展开/收起:grid 0fr→1fr 让未知高度(空态/1–8 行/248px 限高列表)共用一套动画, + 内层 overflow:hidden 只负责裁切;实际列表仍保留自己的纵向滚动。 */ +.sidebar-group-content { + display: grid; + grid-template-rows: 0fr; + opacity: 0; + transition: + grid-template-rows 180ms cubic-bezier(0.32, 0.72, 0, 1), + opacity 120ms ease-out; +} +.sidebar-group-content.is-expanded { + grid-template-rows: 1fr; + opacity: 1; +} +.sidebar-group-content-inner { + min-height: 0; + overflow: hidden; +} /* 会话行状态点:工作中 = 墨色呼吸(与顶栏胶囊头像同一套节拍与时长) */ .sidebar-dot-working { animation: avatar-breathe 3.2s ease-in-out infinite; @@ -987,7 +1005,8 @@ code { } @media (prefers-reduced-motion: reduce) { .sidebar, - .sidebar-inner { + .sidebar-inner, + .sidebar-group-content { transition: none; } .sidebar-dot-working { diff --git a/scripts/check-sidebar-group-scroll.mjs b/scripts/check-sidebar-group-scroll.mjs index 18cfee4e..015997ae 100644 --- a/scripts/check-sidebar-group-scroll.mjs +++ b/scripts/check-sidebar-group-scroll.mjs @@ -101,7 +101,7 @@ function note(text) { const READ_SNAPSHOT = `(() => { const root = document.querySelector("[data-sidebar-scroll-root]"); if (!root) return JSON.stringify({ error: "no-scroll-root" }); - const lists = [...root.querySelectorAll("[data-sidebar-session-list]")]; + const lists = [...root.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')]; return JSON.stringify({ root: { clientH: root.clientHeight, @@ -113,8 +113,9 @@ const READ_SNAPSHOT = `(() => { lists: lists.map((el, i) => { const cs = getComputedStyle(el); const rows = [...el.querySelectorAll("[data-session-id]")]; - // 分组标题行:用 DOM 相邻关系(分组根 div 里的 aria-expanded 按钮),不按文案匹配 - const title = el.parentElement ? el.parentElement.querySelector("button[aria-expanded]") : null; + // 分组标题行:从动画容器回到它的前一个兄弟 ProjectRow,不按文案匹配 + const content = el.closest("[data-sidebar-group-content]"); + const title = content?.previousElementSibling?.querySelector("button[aria-expanded]") ?? null; return { i, scrollable: el.dataset.scrollable === "true", @@ -144,7 +145,7 @@ function snapshot() { function positions() { return evalJs(`(() => { const root = document.querySelector("[data-sidebar-scroll-root]"); - const lists = [...root.querySelectorAll("[data-sidebar-session-list]")]; + const lists = [...root.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')]; return JSON.stringify({ outer: root.scrollTop, lists: lists.map((l) => l.scrollTop) }); })()`).then(JSON.parse); } @@ -153,7 +154,7 @@ function reset() { return evalJs(`(() => { const root = document.querySelector("[data-sidebar-scroll-root]"); root.scrollTop = 0; - for (const l of root.querySelectorAll("[data-sidebar-session-list]")) l.scrollTop = 0; + for (const l of root.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')) l.scrollTop = 0; return true; })()`); } @@ -161,7 +162,7 @@ function reset() { /** 把某个内层预置到中部(用于「非边界位置」的手势;不是用它代替 wheel) */ function presetListTop(index, cssExpr) { return evalJs(`(() => { - const l = document.querySelectorAll("[data-sidebar-session-list]")[${index}]; + const l = document.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')[${index}]; l.scrollTop = ${cssExpr}; return l.scrollTop; })()`); @@ -220,9 +221,10 @@ async function wheel(selectorExpr, deltaY, times = 1) { await settle(); } -const listExpr = (i) => `document.querySelectorAll("[data-sidebar-session-list]")[${i}]`; +const listExpr = (i) => + `document.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')[${i}]`; const titleExpr = (i) => - `document.querySelectorAll("[data-sidebar-session-list]")[${i}].parentElement.querySelector("button[aria-expanded]")`; + `${listExpr(i)}.closest("[data-sidebar-group-content]").previousElementSibling.querySelector("button[aria-expanded]")`; /* ---------- 1. 结构与尺寸契约 ---------- */ console.log("=== 1. 结构与尺寸 ==="); @@ -381,7 +383,7 @@ console.log("\n=== 3. 边缘淡出 ==="); function readFade(index) { return evalJs(`(() => { const root = document.querySelector("[data-sidebar-scroll-root]"); - const el = document.querySelectorAll("[data-sidebar-session-list]")[${index}]; + const el = document.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')[${index}]; const cs = getComputedStyle(el); const r = el.getBoundingClientRect(); const a = root.getBoundingClientRect(); @@ -421,7 +423,7 @@ for (const l of overflow) { s = await (async () => { await evalJs( - `(() => { const el = document.querySelectorAll("[data-sidebar-session-list]")[${l.i}]; el.scrollTop = Math.round((el.scrollHeight - el.clientHeight) / 2); return true; })()`, + `(() => { const el = document.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')[${l.i}]; el.scrollTop = Math.round((el.scrollHeight - el.clientHeight) / 2); return true; })()`, ); await settle(); return readFade(l.i); @@ -439,7 +441,7 @@ for (const l of overflow) { s = await (async () => { await evalJs( - `(() => { const el = document.querySelectorAll("[data-sidebar-session-list]")[${l.i}]; el.scrollTop = el.scrollHeight; return true; })()`, + `(() => { const el = document.querySelectorAll('[data-sidebar-group-content="expanded"] [data-sidebar-session-list]')[${l.i}]; el.scrollTop = el.scrollHeight; return true; })()`, ); await settle(); return readFade(l.i);