Repository navigation
Conversation
MiniMax-AI#131 合入后 0.9.0 的 pet-app.js 仍在用 screenX 算拖拽目标。桌宠页面跑在宿主的 webview 里,那里的 screenX 是相对 webview 自己的坐标,不是屏幕绝对坐标。旧公式 把窗口内的指针偏移又加回抓取时的窗口原点,形成"窗口跟随指针 → 窗口内位移回缩 → 目标回弹 → 窗口前冲"的振荡环,窗口就在抓取点附近无限往返,宿主旧构建的日志里是 1580 与 1700 交替。每帧 pointermove 都发 win.update,宿主窗口 IPC 一直不空,模式 切换撞上正在切换或重建页面的 tab 就被判 busy。 改成用宿主当前的窗口原点加指针在窗口内的位移算目标位置。目标与原点相同就不再发 win.update,按住不动时一个更新都不发,反馈环没有起点;宿主拒绝移动时把原点回滚 到上一个宿主接受的位置,从那里继续。跑动方向的动画改用同一个目标位置,窗口内 坐标抖动不再让方向来回翻。宿主没有报出窗口位置时仍按指针位移播放跑动动画,只是 窗口不动。 本分支原先基于 0.8.0,与 MiniMax-AI#131(0.9.0)冲突,已重放到 0.9.0 之上:版本 0.9.0 → 0.9.1,两个 README 补 0.9.1 说明。新增 dev/window-drag.test.mjs,8 条断言覆盖按住 不动、跟随指针、被拒后继续、方向动画与激活阈值。 门禁读数:仓库根 npm run check 92/92 通过;包内 npm test 268 项中 267 项通过,1 项 失败是 dev/pet-art-validation.test.mjs 在 Windows 上把 file:// URL 直接交给 execFile 导致的路径拼接问题,涉及文件本次未改动,与本改动无关;npm run check:package 显示 41 个文件与源码构建逐字节一致。
387718e to
29b22ac
Compare
|
已按 #131 重整这个 PR。#131 在本 PR 之后 12 分钟合入,把 minimax-pet 升到 0.9.0 并重写了 现在分支已基于 0.9.0 重放,PR 状态回到 MERGEABLE。 改动:
门禁读数:
未验证:
|
|
Thanks for rebasing onto 0.9.0. The rebase looks clean: only the three drag functions changed, and the version reads 0.9.1 everywhere. There's one issue in A rejected update can roll the origin back past a later one that succeeded. On rejection the code sets Fast drags still overshoot a little. Suggested fix: allow one We found this by reading the code and running it against a simulated host, not by dragging a real MiniMax Code window. |
按 MiniMax-AI#132 的评审意见改的。原来的实现有两个问题。 一是被拒的更新会回退原点。更新失败时把原点还原成这次更新之前的乐观值,而此时 可能已经有一次更晚的更新被宿主接受并落地。原点回退到更早的位置,下一次目标位置 就从那里算,窗口往回跳。评审给的例子:从 1000 开始,1030 的更新被拒、1090 的更新 被接受,原点被还原成 1000,下一次落在 980 而不是 1070,跳了约 110px。 二是快速拖拽会多算一段位移。原点在宿主移动窗口之前就写成了目标位置,而 clientX 要等窗口真的动了才变。下一次 pointermove 先到时,没落地的位移被算了两遍,窗口冲 过头,指针停下后又弹回来。 改成同一时刻只有一个 win.update 在途:更新期间只保留最新的目标位置,更新返回后 再发;目标位置一律按宿主确认过的窗口原点算,不再用乐观原点;更新被拒时回落到 宿主确认的位置(app.winPos)。宿主在更新应答里带回窗口位置,成功后直接用应答里 的位置作为新的原点。窗口位置的算法没变:指针在窗口内的位移加宿主确认的窗口原点。 新增两条断言锁住这两个场景: - a refused update does not roll the origin back past a later accepted one 拒绝一次、接受一次之后,下一次目标位置是 1070,不是 980。 - a fast drag counts a movement that has not landed yet only once 两次 pointermove 在宿主应答之前到达时,窗口落在 1060,不是 1090。 两条断言在改之前的实现上都会失败,分别落到 1120 和 1090;改之后通过。 门禁读数:仓库根 npm run check 92/92 通过;包内 npm run check:package 41 个文件与 源码构建逐字节一致;包内 npm test 270 项中 269 项通过,唯一失败仍是 dev/pet-art-validation.test.mjs 在 Windows 上把 file:// URL 直接交给 execFile 造成 的路径拼接问题,该文件本次没有改动。 未验证:没有在真实 MiniMax Code 桌面窗口里实际拖拽,也没有实测宿主对窗口更新的 应答时序。
|
两条都复现了,都按建议改成「同一时刻只有一个 被拒的更新不再回退原点。 原来失败时把原点还原成这次更新之前的乐观值;如果那时已经有一次更晚的更新被接受并落地,原点就退到了更早的位置,下一次目标位置从那里算,窗口往回跳。现在更新在途时只保留最新的目标位置、更新返回后再发,所以不会再出现「更晚的更新先落地、更早的拒绝后到达」这种情况;被拒时回落到宿主确认的位置( 快速拖拽不再把未落地的位移算两遍。 原来在宿主移动窗口之前就把原点写成了目标位置,而 新增两条断言锁住这两个场景:
两条断言在改之前的实现上都会失败,分别落到 1120 和 1090;改之后通过。 窗口位置的算法没有变:指针在窗口内的位移加宿主确认的窗口原点。「按住不动时窗口停在原地」和「被拒的移动从宿主保留的位置继续」这两条性质也保持不变。 门禁读数:
未验证:没有在真实 MiniMax Code 桌面窗口里实际拖拽,也没有实测宿主对窗口更新的应答时序。 |
|
Thanks for the update. Before we go further, could you share a screen recording of the jitter? Two short clips would be ideal: one on current main (minimax-pet 0.9.0) and one on this branch, each dragging the popped-out pet window the same way with a physical mouse. Please also note your OS and display scale. We haven't been able to reproduce the jitter when dragging on our side, so a recording would help us confirm the problem and that this change fixes it. |
|
One more issue from reviewing 56096b5. If a pointermove arrives while a Suggested fix: while a request is in flight, just record the latest pointer position. When it resolves, recompute the target from the Host-confirmed window position plus the latest pointer offset. On pointerup, wait for the in-flight request to finish before sending the final position. It would also help to add a test for the case where the window has moved, the reply is still pending, and a pointermove arrives. |
按 MiniMax-AI#132 的评审意见改的。上一版用「宿主确认的窗口原点 + 指针在窗口内的位移」算目标 位置,在宿主已经移动窗口、但更新还没应答的时候,这个原点是旧的。评审给的例子:窗口 从 1000 被请求移到 1030、应答还没回来时指针又动了 10px,下一次更新发的是 1010 而不是 1040,窗口先往回退一步再被拉回来。反过来,在同一次动画帧里宿主还没来得及应用上一次 移动时到达的 pointermove,用同一个旧原点算出来的目标又会偏到指针前面。 改成把窗口放到「指针在屏幕上的位置减去抓取时的偏移」。指针是在窗口内测量的,它的 屏幕位置取决于事件测量时所在的窗口原点,所以要判断那个原点是宿主确认的那个,还是 宿主已经移动到、但应答还没回来的那个。判断依据是指针自己:两次事件之间指针只走几 个像素,窗口一次移动要远得多,哪个候选原点的指针位移更小就用哪个。 于是三种时序都对:宿主还没移动窗口时到达的 pointermove、宿主已经移动而应答未回时 到达的 pointermove、以及同一帧内连续到达的 pointermove。上一版的另外两个问题仍然 成立:同一时刻只有一个 win.update 在途,更新期间只保留最新的目标位置并按宿主确认 的位置重算,被拒的更新回落到宿主确认的位置(app.winPos),不会把窗口回退到更早一次 成功移动之前。 新增一条断言锁住评审给的场景: - a pointermove that arrives while the Host moves the window does not step it back 宿主把窗口移到 1030 但还没应答时指针再动 10px,下一次更新发 1040,不是 1010。 这条断言在改之前的实现上失败,实际发的是 1010;改之后通过。 门禁读数:仓库根 npm run check 92/92 通过;包内 npm run check:package 41 个文件与 源码构建逐字节一致;包内 npm test 271 项中 270 项通过,唯一失败仍是 dev/pet-art-validation.test.mjs 在 Windows 上把 file:// URL 直接交给 execFile 造成 的路径拼接问题,该文件本次没有改动。 未验证:没有在真实 MiniMax Code 桌面窗口里实际拖拽,也没有实测宿主对窗口更新的 应答时序。
|
Thanks for the quick turnaround. Choosing the origin by distance is ambiguous by nature, so tuning it won't fully fix this. Please go with the approach from our earlier comment instead: while a request is in flight, only record the latest client coordinates. When the reply arrives, recompute the target from the Host-confirmed origin plus the pointer offset. On pointerup, wait for the in-flight step to finish before sending the final one. Please also add tests with s≈v and s≈2v. Before the next round, could you share the screen recordings we asked for? We'd like to confirm the jitter with a physical mouse first. |
维护者指出 dragTarget() 用「指针位移最小」在两个候选原点之间择一,在匀速拖拽 时会选错(s < 2v),要求改用他们早前评论里的方案: - 窗口更新在途时只记录最新的指针坐标,不再据此算目标。 - 宿主应答后,用「宿主确认的窗口原点 + 指针相对抓取点的偏移」重算目标。 - 抬手时等在途的那次更新结束,再把最终位置发出去。 - 只有在请求之后又来了新的指针事件时才重发。指针停住时,宿主确认的原点加上 一个已经过期的偏移会让窗口自己往前爬;来了一条新指针事件,才说明指针可能 真的动了。 测试: - s ≈ v:在途位移等于一个指针步长时,目标位置仍落在指针上。 - s ≈ 2v:在途位移等于两个指针步长时,目标位置仍落在指针上。 - 抬手时等在途更新结束,最终位置才发出去。 - 快速拖拽与「被拒的更新不回退原点」两条按宿主在下一帧才移动窗口重写。 对照:同一套新测试跑在上一版实现(d779e2e)上失败 4 条——快速拖拽只发 1030、 s≈v 只发 1010、s≈2v 发成 1020→1010 往回退、抬手只发 1030;本版 13 条全过。 门禁: - 仓库根 npm run check:92/92 通过,0 失败。 - 包内 npm run check:package:41 个文件与源码构建逐字节一致。 - 包内 npm test:273 项中 272 项通过。唯一失败仍是 dev/pet-art-validation.test.mjs 在 Windows 上把 file:// URL 交给 execFile 造成的路径拼接问题(拼出 F:\F:\...), 该文件本次没有改动。 未验证:没有在真实 MiniMax Code 桌面窗口里实际拖拽,也没有实测宿主对窗口更新 的应答时序;维护者要的屏幕录像还没提供。
|
已按你们早前评论里的方案重写(提交 改了什么
新增测试(
对照:同一套测试跑在上一版实现( 测试脚手架的一处改动:宿主在收到更新后的下一帧才移动窗口,应答在这之后才回来,所以这几条用例改成显式调用 门禁读数
未验证 / 还没做的
|
Fixes the desktop pet's drag in the popped-out window. The pet page runs inside a Host
<webview>, wherescreenXis relative to the webview itself, so the old formula fed the window-relative pointer offset back into the window origin and the window kept bouncing around the grab point. The target position is now the Host's window origin plus the pointer's offset inside the window, and the page stops sending window updates as soon as the target stops changing.Tested environment and results
MiniMax Code 3.1.14 (inside build 155) on Windows 11 (10.0.19045), Host app installed locally.
npm run check(validate + test): 92 tests, 92 pass, 0 fail.npm run check:package: 41 files match the source build byte for byte.npm test: 259 tests, 258 pass, 1 fail. The failure ispet-art-validation.test.mjs, "the standalone CLI returns machine-readable success and failure with exit status": on Windows that case builds the CLI path fromurl.pathnameand ends up withF:\F:\code\.... It fails the same way without this change and was left alone.dev/window-drag.test.mjs: 8 assertions covering a held pointer, pointer tracking, continuing from the position the Host kept after a refused move, the direction animation and the activation threshold. It runs the same assertions forscreenXread as a local offset and as an absolute coordinate.1030 -> 1000 -> 1030.not_permittedand the window keeps the position the Host kept.What was not verified
{x, y}, and the Host keeps the window width for a patch withoutwidth, so the drag loop cannot widen the window on its own. The two reported symptoms share the same engine, the per frame update loop, and that loop is gone now, but this variant has no separate reproduction and no separate comparison.Coordinate space and platform assumptions
The formula mixes two things: the pointer's offset inside the window (a
clientXdifference, CSS pixels) and the window origin the Host reports (win.getState(),getContentBounds(), Electron DIP). Both are logical pixels, so the ratio is 1:1 on Windows at any display scale and on macOS Retina.<webview>inside it, so the guest renders at zoom 1 and one CSS pixel is one DIP. A page zoom on the guest would change that ratio; the zoom call sites in the Host bundle (webui tab zoom, browser device preview, popup inheritance) do not reach that path today.frame: false). The Host persists the outer bounds but reports the content bounds to the page, and the two share an origin only while the window has no frame.not_permitted, at least 40px of the window must overlap a display work area) rather than clamped, so the rollback keeps the position the Host kept on every platform. Coordinates for a monitor left of or above the primary one are passed through unchanged.Warnings from
npm run checkNone, the run reports no warnings. The one failing package test is listed above.
New access or higher minimum client version
None. No permission declaration changed, no new manifest field, the minimum client version stays 3.1.6.
Written by MiniMax Code (M3.1-Flash)
Root cause
The popped out window is a Host BrowserWindow that loads
webview-host/index.html, and that page mounts the guest page in a<webview>tag (Hostwebview-host.js). Inside a<webview>thescreenXof a pointer event is relative to the webview's own bounds, not to the screen. The drag log from the real Host shows it directly: on the pointer down framescreenXandclientXare both 138.The old code computed
dx = e.screenX - d.startXand added it to the window origin captured at grab time. The window follows the pointer, so the pointer's offset inside the window shrinks back to the grab point, the computed target falls back to the grab origin, the window jumps back, the offset grows again and the next frame pushes it forward. The window bounces between the grab position and the target position forever. The Host's own log of the old build shows exactly that, 1580 and 1700 alternating.busycame after that. The old code sentwin.update({x, y})on every pointer move, so the Host's window IPC never went idle, and a mode switch that lands on a tab which is switching or rebuilding a page is answered withbusy(updateMiniAppWindowin the Host).The fix
sources/minimax-ai/minimax-pet/miniapp/client/pet-app.jsonSpritePointerDownrecordsgrabX/grabYfromclientX/clientY, the window origin at grab time asbase, andoriginas the origin the app tracks optimistically.screenX/screenYare gone.onSpritePointerMovecomputestarget = origin + (client - grab). When the target equals the origin the handler returns without sending an update, so a held pointer sends nothing at all and the feedback loop has no place to start. The origin is updated optimistically and rolled back to the position the Host kept when an update is refused, includingnot_permittedfor a target off screen. The threshold that activates the drag uses the travel againstbase.updateDragDirectionuses the target x instead of the pointer coordinate, so the running animation no longer flips while the pointer moves inside the moving window.The formula needs only the pointer's relative movement inside the window and the Host's window origin. In the popped-out window the guest page is a
<webview>, soclientX/clientYare the offset inside that window and not a screen position; the Host's origin is what turns that offset into a screen position. The inline panel has no separate window, so the drag only moves a window in the popped-out mode.Version
0.8.0 to 0.8.1 in
plugin.json,package.json,package-lock.jsonand the version line inindex.html, plus one entry in each README.修掉独立窗口里拖动桌宠时的自我移动。桌宠页面跑在宿主的
<webview>里,那里的screenX是相对 webview 自己的坐标,旧公式把这个窗口内偏移又加回窗口原点,窗口就在抓取点附近来回弹。目标位置改成宿主窗口原点加指针在窗口内的位移,目标不再变化时页面也不再发窗口更新。测试环境与结果
MiniMax Code 3.1.14(内部构建 155),Windows 11(10.0.19045),本机安装的宿主。
npm run check(validate + test):92 项测试,92 通过、0 失败。npm run check:package:41 个文件与源构建逐字节一致。npm test:259 项测试,258 通过、1 失败。失败的是pet-art-validation.test.mjs的「独立 CLI 返回机器可读的成功与失败并带退出码」用例:Windows 上该用例用url.pathname拼 CLI 路径,拼成了F:\F:\code\...。不带本次改动时同样失败,属于既有问题,没有动它。dev/window-drag.test.mjs8 条断言,覆盖按住不动、跟随指针、被拒后从宿主保留的位置继续、方向动画与激活阈值,并对screenX的局部偏移与绝对坐标两种语义各跑一遍。1030 -> 1000 -> 1030的振荡签名。not_permitted,窗口停在宿主保留的位置。没有验证到的
{x, y},宿主对不含width的补丁保持窗口宽度不变,所以拖拽流本身撑不宽窗口。两种症状共用同一个引擎,也就是每帧发更新的反馈环,这个环现在断了,但这一支没有单独的复现和对照。坐标空间与平台前提
公式里混了两样东西:指针在窗口内的偏移(clientX 差值,CSS 像素)和宿主回报的窗口原点(
win.getState()、getContentBounds()、Electron 的 DIP)。两边都是逻辑像素,比例 1:1,Windows 任意显示缩放和 macOS Retina 都一样。<webview>,guest 以缩放 1 渲染,1 CSS 像素等于 1 DIP。给 guest 加页面缩放会改变这个比例;宿主整包里的缩放调用点(webui 标签页缩放、浏览器设备预览、弹窗继承缩放)目前都不在这条路上。frame: false)。宿主持久化的是外框边界,回报给页面的是内容区边界,只有窗口没有边框时这两者的原点才重合。not_permitted,窗口至少要有 40px 落在某个显示器的可用区里),不是夹取,所以各平台上回滚后都停在宿主保留的位置。副屏在主屏左边或上边时的负坐标原样传过去。npm run check的剩余告警没有。该次运行不报告警,唯一失败的那条包内测试已列在上面。
新增权限或更高的最低客户端版本
没有。权限声明未变,没有新增 manifest 字段,最低客户端版本仍是 3.1.6。
Written by MiniMax Code (M3.1-Flash)
根因
弹出的独立窗口是宿主的 BrowserWindow,加载
webview-host/index.html,那个页面把 guest 页面挂在<webview>标签里(宿主webview-host.js)。在<webview>里,pointer 事件的screenX是相对 webview 自身边界的,不是屏幕坐标。真实拖拽日志能直接看到:按下那一帧screenX与clientX都是 138。旧代码算
dx = e.screenX - d.startX,再加到抓取时记下的窗口原点上。窗口跟着指针走,指针在窗口内的位移就缩回抓取点,算出来的目标位置退回抓取原点,窗口弹回去,位移又变大,下一帧再把窗口推出去。窗口就这样在抓取位和目标位之间无限往返,宿主旧构建的日志里正是 1580 与 1700 交替。busy是之后才出现的。旧代码每个 pointermove 都发一次win.update({x, y}),宿主窗口 IPC 一直不空,模式切换撞上正在切换或重建页面的 tab 就会被判busy(宿主updateMiniAppWindow)。改法
sources/minimax-ai/minimax-pet/miniapp/client/pet-app.jsonSpritePointerDown用clientX/clientY记 grabX/grabY,用抓取时的窗口原点作 base,另记一个乐观跟踪的 origin。screenX/screenY 不再出现。onSpritePointerMove算target = origin + (client - grab)。target 与 origin 相同时直接 return,一个更新都不发,按住不动就彻底安静,反馈环没有起点。origin 乐观更新,发出的更新被拒时(包括目标越界的not_permitted)回滚到宿主保留的位置。激活拖拽的阈值改用相对 base 的位移。updateDragDirection改用目标 x,窗口内坐标抖动不再让跑动方向来回翻。这个公式只需要指针在窗口内的相对位移和宿主的窗口原点。独立窗口里 guest 页面是
<webview>,clientX/clientY 是它在窗口内的偏移而不是屏幕位置,是宿主给的原点把这个偏移换算成屏幕位置。内联面板没有独立窗口,所以只有弹出模式才需要移动窗口。版本
plugin.json、package.json、package-lock.json与index.html的版本文案从 0.8.0 改为 0.8.1,两个 README 各加一条说明。Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.