feat: make activity panel draggable + slow snapshot poll to 3000ms - #34
feat: make activity panel draggable + slow snapshot poll to 3000ms#34Liangebra wants to merge 3 commits into
Conversation
Drag the AgentTeams floater by its header to reposition it: mousedown→capture→move updates left/top (clamped to viewport) and the position persists to localStorage (dsh-agent-teams:panel-pos). Default stays the top-right corner; a saved position switches the aside to left/top anchoring. Also raise POLL_MS 1000→3000 for the host snapshot route (two /state calls per poll) to cut request load ~3x while keeping events instant via polling-as-backstop. Pointer-down on the close button is ignored so clicks still reach it.
There was a problem hiding this comment.
Pull request overview
This PR enhances the AgentTeams ActivityPanel floater UX by adding draggable positioning with persistence, and reduces backend load by slowing the snapshot polling cadence.
Changes:
- Make the activity panel draggable via its header, persisting the panel position in
localStorage. - Clamp dragged positions to keep the panel reachable on-screen.
- Reduce snapshot polling cadence from 1000ms to 3000ms to lower request volume.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/client/ActivityPanel.tsx | Adds draggable position state/persistence + pointer handlers; increases POLL_MS to 3000ms. |
| src/client/ActivityPanel.module.css | Updates header cursor/touch behavior and disables entry animation after dragging. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| useEffect(() => { | ||
| try { | ||
| if (panelPos === null) localStorage.removeItem(PANEL_POS_KEY) | ||
| else localStorage.setItem(PANEL_POS_KEY, JSON.stringify(panelPos)) | ||
| } catch { | ||
| // Privacy mode / quota: non-fatal. | ||
| } | ||
| }, [panelPos]) |
There was a problem hiding this comment.
已修复:localStorage 持久化改用防抖(250ms settle)——拖动过程中的高频 setPanelPos 只会在手势停稳后写一次,不再每次 pointer move 同步写 localStorage 造成 jank;panelPos 为 null 时立即移除键,卸载时 flush 未写定时的待写。见 commit 128b4a6。
| function clampPanelPos(pos: { x: number; y: number }, panelWidth: number, panelHeight: number): { x: number; y: number } { | ||
| const minX = -(panelWidth * 0.6) | ||
| const maxX = window.innerWidth - 24 | ||
| const minY = 8 | ||
| const maxY = window.innerHeight - 24 | ||
| return { | ||
| x: Math.max(minX, Math.min(maxX, pos.x)), | ||
| y: Math.max(minY, Math.min(maxY, pos.y)), | ||
| } | ||
| } |
There was a problem hiding this comment.
已修复:clampPanelPos 现同时使用 panelWidth 与 panelHeight——至少保留 MIN_VISIBLE(48px) 在屏内,maxX/maxY 不再只留 ~24px,避免整块几乎拖出屏幕;不再有未用参数。见 commit 128b4a6。
| const onHeaderPointerDown = (event: ReactPointerEvent<HTMLElement>): void => { | ||
| const target = event.target as HTMLElement | ||
| if (target.closest('button') !== null) return | ||
| const el = panelRef.current | ||
| if (el === null) return | ||
| // Capture on the header so move/up keep flowing to it even when the pointer | ||
| // leaves the panel mid-drag. | ||
| const handle = event.currentTarget as HTMLElement | ||
| handle.setPointerCapture(event.pointerId) |
There was a problem hiding this comment.
已修复:onHeaderPointerDown 先判 event.button === 0(仅主键),并调用 preventDefault() 抑制拖动过程中的文本选择;右键/中键不再误触拖拽,close 按钮守卫保持不变。见 commit 128b4a6。
When the panel has been dragged, the collapsed badge now uses the same saved left/top (right:auto) so collapsing/expanding keeps the floater where the user left it, instead of snapping back to the top-right corner.
|
更正说明 (accuracy fix): 本 PR 对轮询的表述有一处需要更正——此前 PR 描述 / 首次提交信息里 "SSE 保持即时性 / polling-as-backstop" 的写法有误:AgentTeams 活动面板没有 SSE / 事件推送通道, 实际情况:
PR 描述已同步更正(见 Summary 与 备注)。此评论用于澄清首次提交信息里 "polling-as-backstop" 的措辞。 |
- debounce the localStorage panel-pos persistence (~250ms) so a drag does not do a synchronous write on every pointer move (removes jank) - clampPanelPos now uses both panel dimensions: at least a MIN_VISIBLE chunk stays on-screen (was ~24px), matching the comment - drag starts only on the primary button and calls preventDefault() so right/middle clicks are ignored and text selection is suppressed
PR: 活动面板可拖动 + 轮询降频
Summary
定位从
right/bottom切换为left/top(拖动后 inline 覆盖),并 localStorage 持久化(键
dsh-agent-teams:panel-pos),刷新/重开后保持在原地;未拖动时维持默认右上角。折叠态 badge 也跟随同一保存位置,不跳回右上角。
POLL_MS1000→3000ms。面板每轮Promise.all拉
/state与/state?archived=1,原 1s 一轮实为 2 请求/s;改为 3s 一轮后快照请求负载降约 3 倍。面板的
/state是唯一刷新机制(没有 SSE),这是纯轮询降频——最大刷新延迟从 1s 增至 3s,对右上角状态监视面板可接受。
改动
src/client/ActivityPanel.tsx:新增PANEL_POS_KEY/readPanelPos/clampPanelPos辅助函数、组件内
panelPos/panelRef/dragRef状态、onHeaderPointerDown/Move/Up拖拽处理器、<aside>加 ref/data-dragged/inline 位置、<header>加 pointer 处理器与 title;CollapsedBadge接收panelPos并应用同一 left/top;POLL_MS=3000。src/client/ActivityPanel.module.css:.panelHead加cursor:grab/touch-action:none,.panel[data-dragged=true]关动画。(
lib/为构建产物且被 .gitignore 忽略——本 PR 仅改源码,构建方pnpm build重新生成。)验证
lib/client.js(编译产物)后硬刷新 DSH 页面:PANEL_POS_KEY/clampPanelPos/data-dragged/cursor:grab与POLL_MS=3e3;/plugins/dsh-agent-teams/state轮询间隔 ≈3000ms(从原 1s 降为 3s),无 console 报错;释放结束、localStorage 写入位置;badge reflow 实测 left/top 精确跟随;关闭按钮上的 pointerdown 不触发拖拽。
备注
/state是纯轮询(本插件无 SSE/事件推送通道),POLL_MS是它的唯一刷新定时器;由 1000ms 提至 3000ms 后最大刷新延迟为 3s(此前为 1s),对右上角状态监视可接受,快照负载降约 3 倍。