Repository navigation
Conversation
- 面板顶部新增把手:pointer 拖动移动(localStorage 记忆),双击「用量」按钮复位。 - 偏移只做 transform 相对位移,边界夹取复用锚点同一个 containingBlock(), 面板四条边至少保留 48px 在包含块内(皮肤 transform/backdrop-filter 下同样正确)。 - 新增 src/client/panel-position.ts(无 DOM 依赖的读取/夹取纯函数)与 tests/panel-position.test.mjs;CHANGELOG 中英 [未发布] 段; LOCAL_PATCHES.md 记录本次并入上游 1.1.7 与补丁重放。
Max-Samson
left a comment
There was a problem hiding this comment.
Reviewed at d44f73f. The drag feature is narrowly scoped, and local verification passes all 50 tests; however, the new interaction has four reproducible correctness issues below. Please address them before merging.
Verification: isolated headless Chromium running the actual UsageIndicator and plugin CSS, with data hooks and panel contents mocked. This is component-level verification, not a full live DSH installation. npm pack --dry-run also passes.
|
|
||
| /** 读取已持久化的偏移;缺失、坏 JSON、非有限数值一律回 {0,0}。 */ | ||
| export function readPanelOffset( | ||
| storage: Pick<Storage, 'getItem'> | undefined = typeof localStorage === 'undefined' ? undefined : localStorage, |
There was a problem hiding this comment.
[P1] Access localStorage inside the guarded function body
When browser policy denies storage access, the window.localStorage getter itself throws SecurityError. The default argument is evaluated before the function body, so the try/catch below cannot handle it. UsageIndicator calls readPanelOffset() during its useState initializer; in the Chromium reproduction the indicator does not render at all. The existing currency storage reads guard this access inside try, so this is a new regression.
Please move acquisition of the default storage into the try block and add a regression test for a throwing localStorage getter (not only a missing global or a throwing getItem).
| right: rect.right - offsetRef.current.x, | ||
| bottom: rect.bottom - offsetRef.current.y, | ||
| } | ||
| final = clampPanelOffset(next, base, dragBounds()) |
There was a problem hiding this comment.
[P2] Re-clamp restored offsets when the panel opens or its bounds change
This clamp runs only through moveDrag. Restored positions, window resize, and anchor changes can therefore leave the panel entirely outside the viewport. Reproduction: at a 1200px viewport drag right and save offset {x:900,y:0}, then shrink to 400px. The panel's left edge becomes 908px, and reloading/reopening retains that off-screen position. The user cannot reach the panel or handle without knowing the double-click reset gesture.
Please validate the restored offset after the panel can be measured and re-clamp when the anchor/bounds change. Add resize and narrow-window restoration coverage.
| }, [expanded]) | ||
|
|
||
| // 卸载时兜底恢复:拖动中途组件被卸载不该把整页文字设成不可选。 | ||
| useEffect(() => () => { document.body.style.userSelect = '' }, []) |
There was a problem hiding this comment.
[P2] End the drag when the popover closes, not only when the indicator unmounts
Start dragging, press Escape while holding the pointer, then release. Escape removes the popover/handle but leaves UsageIndicator mounted, so neither endDrag nor this component-unmount cleanup restores the body style. Chromium confirms the panel is gone while document.body.style.userSelect remains 'none'; conversation text remains unselectable.
Please clear active drag state and restore the previous body userSelect value when the popover closes or pointer capture is lost, as well as on component unmount. Add an Escape-during-drag regression test.
| const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max) | ||
| const minX = bounds.left - panel.right + minVisible | ||
| const maxX = bounds.right - panel.left - minVisible | ||
| const minY = bounds.top - panel.bottom + minVisible |
There was a problem hiding this comment.
[P2] Keep the only drag handle reachable at the upper boundary
The vertical minimum allows the panel's top to move above the viewport as long as its bottom 48px remain visible. The handle is sticky inside the popover's scroll container, not to the viewport, so it moves off-screen with the panel. In Chromium, dragging upward leaves the panel at y=-371 and the handle at y=-366; after releasing the pointer the panel cannot be dragged back. Only the reset gesture recovers it.
Please constrain the handle/header to remain within the visible bounds, rather than only retaining the panel's bottom edge, and cover release/re-drag at the upper boundary.
Summary
Implements the behavior proposed in #16: the panel can now be dragged out of the way of the conversation.
localStorageand survives a reload.transform/backdrop-filterare handled correctly; the panel always keeps at least 48px of itself on screen.transform: translate(); the default anchor position and the automatic follow behavior are unchanged.Only
src/, both CHANGELOGs and tests are touched.Verification
npm run verify— 50/50 (the 46 existing tests plus 4 new cases intests/panel-position.test.mjs)npm pack --dry-runlib/files includedCHANGELOG.mdupdated for user-visible changesVerified in a live DSH web profile (this branch link-installed, page refreshed): the drag handle, the position surviving a reload, and the double-click reset all behave as described. Happy to adjust the interaction shape (for example the automatic-avoidance alternative from the issue) if you prefer it.