fix(niri): stabilize pet drag and peek coordinates - #2520
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
🚧 Files skipped from review as they are similar to previous changes (1)
Walkthrough本次变更将 Niri 物理裁剪下的模型过渡、桌面几何、返回球拖拽与 Live2D peek 统一到虚拟坐标体系,并补充裁剪同步、控件抑制及测试验证喵。 ChangesNiri 虚拟坐标与交互适配
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
static/avatar/avatar-ui-buttons/methods-return.js (1)
853-885: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick wintoken 赋值太晚了,中间那段是"状态已开但 token 未更新"的窗口喵!
第 853 行算出
safetyToken = dragSafetyToken + 1,但直到第 885 行才真正写回dragSafetyToken。问题是这中间:
- 第 854 行
startDragActivity(safetyToken, ...)已经用新 token 记好了dragActivity.safetyToken- 第 856-858 行同步派发了
return-ball-drag-startCustomEvent- 第 859 行
isDragging = true已经打开如果那个 CustomEvent 的同步监听器(或
_restoreNekoIdleCat1EdgePeekBeforeDrag内部)反手触发了cancelDragState(),就会走到:
cancelDragState读到的safetyToken是旧值(第 697 行)finishDragState的第 637 行safetyToken !== dragSafetyToken比较——两边都是旧值,通过finishDragActivity的第 621 行dragActivity.safetyToken !== safetyToken——新 vs 旧,不匹配,返回null- 回到
finishDragState第 642 行 early return →data-dragging和点击抑制都没清结果就是返回球卡在拖拽态,点也点不动,只能等 5 秒兜底定时器……而那条兜底路径(第 685 行
if (moved && !dragCropHoldPending) return;)在 moved 的情况下还是直接放弃的哦。真是环环相扣的坑呢喵~🔧 把 token 提前落地
const safetyToken = dragSafetyToken + 1; + // Publish the new session token before any state flips or event + // dispatch, so a synchronous re-entrant cancel resolves against + // the same token that startDragActivity recorded. + dragSafetyToken = safetyToken; startDragActivity(safetyToken, rect.left, rect.top); _restoreNekoIdleCat1EdgePeekBeforeDrag(container);然后把第 885 行原本的赋值删掉:
container.setAttribute('data-dragging', 'pending'); container.style.cursor = 'grabbing'; - dragSafetyToken = safetyToken; dragSafetyTimer = setTimeout(() => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/methods-return.js` around lines 853 - 885, Move the `dragSafetyToken = safetyToken` assignment immediately after computing `safetyToken`, before `startDragActivity`, `_restoreNekoIdleCat1EdgePeekBeforeDrag`, or `_dispatchNekoIdleReturnBallManualMove` can synchronously trigger cancellation. Remove the later assignment while preserving the existing drag initialization and token values.static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js (1)
1270-1295: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win用可能返回 null 的 helper 换掉了永不为 null 的 DOMRect,走路动画会崩喵!
这两处共享同一个根因:
container.getBoundingClientRect()对任何已挂载元素永远返回对象,而新的_getNekoDesktopVirtualElementRect()在core.js第 117-121 行遇到width <= 0 || height <= 0时会返回null。替换的时候没有补上空值保护哦。最要命的是
_stepNekoIdleCat1Walk第 1294-1295 行:const nextLeft = rect.left + (target.left - rect.left) * ratio;
rect是null就直接TypeError。而这段跑在requestAnimationFrame回调里(第 1299 行),异常一抛,整条行走链路就断了——猫娘会当场僵在原地再也不动,还得靠别的路径才能救回来的说。触发条件也不算罕见:告别/返回切换、
display变化的中间帧、入场动画起始帧,容器宽高都可能瞬时为 0。
static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js#L1270-L1295:在第 1270 行取到rect后立刻加空值守卫,null时取消本次 journey 并 return,避免第 1294-1295 行裸解引用。static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js#L1317-L1322:currentRect同样可能为null(AI 摘要也确认原先的空值保护分支被删掉了),需要为_resolveNekoIdleCat1TargetFacing和_beginNekoIdleCat1WalkActivity恢复保护。🛡️ `_stepNekoIdleCat1Walk` 的守卫
const rect = _getNekoDesktopVirtualElementRect(container); + if (!rect) { + // The virtual rect helper returns null for zero-sized containers, + // unlike getBoundingClientRect(). Bail out instead of dereferencing. + _cancelNekoIdleCat1Journey(button, { resetArt: true, preserveObservers: true }); + return; + } state.facingRight = _resolveNekoIdleCat1TargetFacing(rect, target);🛡️ `_startNekoIdleCat1Walk` 的守卫
const currentRect = _getNekoDesktopVirtualElementRect(walkContainer); + if (!currentRect) return; state.target = target;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js` around lines 1270 - 1295, 为可能返回 null 的虚拟元素矩形补充空值保护:在 _stepNekoIdleCat1Walk 的 static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js:1270-1295 中,取得 rect 后若为空则取消本次 journey 并立即返回,避免后续坐标解引用。在同文件:1317-1322 的 _startNekoIdleCat1Walk 中,为 currentRect 恢复空值分支,在调用 _resolveNekoIdleCat1TargetFacing 和 _beginNekoIdleCat1WalkActivity 前处理 null 并退出。
🧹 Nitpick comments (8)
tests/unit/test_avatar_return_button_idle_tiers_static.py (2)
2596-2601: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value结束定界符硬编码了 12 个空格的缩进,太容易碎了喵~
"}\n };"把源码的具体缩进层级焊死在测试里,_returnButtonDragHandlers对象只要往里/往外挪一层,_source_slice_between就会直接抛ValueError,而且报错原因跟真正的行为回归毫无关系喵。建议换成对缩进不敏感的锚点,比如下一个已知属性名或紧随其后的语句片段喵~
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/test_avatar_return_button_idle_tiers_static.py` around lines 2596 - 2601, Update the _source_slice_between call for the “return button verified crop state handler” so its ending anchor no longer hardcodes 12 spaces of indentation. Use an indentation-insensitive anchor based on the next known property or following statement, while preserving extraction of the cropStateApplied handler.
3769-3780: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value这段 welcome-back toast 的断言跑错测试啦喵~
test_cat1_walk_is_blocked_while_return_ball_drag_is_active_or_pending讲的是"拖拽进行中要挡住 cat1 走路",而这里突然去校验surface-floating-controls.js的欢迎回来提示走不走宿主 bridge,关注点完全不搭嘎喵。以后这条挂了,看名字的人会一脸问号的喵。建议挪到独立的
test_desktop_welcome_back_toast_routes_through_host_bridge()里去喵~🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/test_avatar_return_button_idle_tiers_static.py` around lines 3769 - 3780, Move the welcome-back toast source-order assertions out of test_cat1_walk_is_blocked_while_return_ball_drag_is_active_or_pending and into a dedicated test_desktop_welcome_back_toast_routes_through_host_bridge(). Keep the existing floating_controls_source checks unchanged within the new test, leaving the cat1 drag-blocking test focused only on preventing cat1 movement.tests/unit/live2d_peek_behavior.test.js (1)
101-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
createInlineStyle只建模了setProperty通道,恢复路径可能被放水喵~真实的
CSSStyleDeclaration同时支持style.display = 'none'这种直接属性赋值,而这个 mock 只有setProperty/removeProperty。后果分两种:
- 实现若用直接赋值来"隐藏",断言会挂 → 能被发现,没问题喵。
- 实现若用直接赋值来"恢复"(例如
style.display = ''),mock 完全不记录,getPropertyValue('display')依旧返回旧值,行 679-684 的恢复断言就会假通过喵。建议给 mock 加上
display/pointerEvents的 getter/setter,让直接属性赋值和setProperty走同一份valuesMap,这样两条通道都盖得住喵~🧪 建议的 mock 补强
- return { + const style = { getPropertyValue(name) { return values.get(name) || ''; }, @@ removeProperty(name) { const previous = values.get(name) || ''; values.delete(name); priorities.delete(name); return previous; } }; + ['display', 'pointer-events'].forEach((name) => { + const camel = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); + Object.defineProperty(style, camel, { + get: () => values.get(name) || '', + set: (v) => { values.set(name, String(v)); priorities.set(name, ''); }, + enumerable: true + }); + }); + return style;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/live2d_peek_behavior.test.js` around lines 101 - 126, 增强 createInlineStyle,使 display 和 pointerEvents 的直接属性读写与 setProperty/getPropertyValue 共用 values Map;添加对应 getter 和 setter,并保持现有优先级与 removeProperty 行为不变,确保恢复路径中的直接赋值能被断言捕获。tests/unit/avatar_idle_niri_crop_origin.test.js (2)
197-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win跨整份源码的
[\s\S]*?正则断言太宽松了喵,可能会放水~这些断言直接对
methods-return.js全文做匹配,[\s\S]*?可以横跨任意多个函数边界。也就是说dragSessionId: dragSafetyToken只要在文件里任何地方出现(哪怕在完全无关的另一个 handler 里),断言就会通过喵。这样测试"看起来很严",实际未必守得住回归。建议先像同 PR 的 Python 契约测试那样,用起止标记切出目标代码块(例如
handleMove/cropStateApplied的片段),再在片段内做匹配,这样锚定更准喵~顺带一提,`` 相关的建议人家都尽量塞在这一条里啦,免得刷屏喵。Based on learnings: 作者偏好将同一 PR 的问题集中在一条评论中列出,避免多条零散评论(适用于
**/*.js)。Also applies to: 261-268
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/avatar_idle_niri_crop_origin.test.js` around lines 197 - 220, 收紧 avatar idle return drag 契约测试:先依据 handleMove、cropStateApplied 等起止标记从 methods-return.js 提取各自目标代码片段,再在对应片段内断言 dragSessionId、拖拽尺寸、抓取偏移和释放流程。避免使用可跨越任意函数边界的全文件 [\s\S]*? 匹配,同时保留现有断言覆盖的行为。Source: Learnings
9-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
readFunction的手写大括号计数器有点脆弱喵~两个隐患,人家帮你数出来了喵:
source.indexOf(') {', start)只找第一个) {,一旦被提取函数的参数里出现默认值箭头函数(例如(a = () => {}, b)),签名就会被截错喵。- 计数器不区分字符串、模板字面量与注释里的
{/},将来这些函数内加一句`${x}px`之外的含大括号字符串就会静默截断,测试会以奇怪的方式挂掉喵。现在被提取的几个函数刚好都安全,所以不是阻塞项,但建议至少加一个"提取结果必须能被
new Function解析"的兜底断言,或用ast-grep/acorn之类做结构化提取,别让工具自己长歪了喵~🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/avatar_idle_niri_crop_origin.test.js` around lines 9 - 23, Update readFunction to extract function boundaries robustly instead of relying on the first “) {” and raw brace counting, accounting for nested parameter expressions and braces inside strings, templates, and comments. At minimum, validate the extracted source with new Function before returning it; prefer structured parsing with an existing parser if available. Preserve the current missing-function and unterminated-function assertions.tests/unit/test_live2d_peek_static.py (1)
172-183: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value这些断言应切到
getLive2DPeekViewport的函数体内喵
ViewportW、canvasW、validVw、niriVirtualViewport都属于getLive2DPeekViewport,而_edge_peek_source()目前切的是LIVE2D_PEEK_TRIGGER_RATIO之后到首个/**之间,还包含大量边锚、渲染、显示上下文等无关内容喵~建议用viewport_source来断言,边界更精准也更抗重构喵~🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/test_live2d_peek_static.py` around lines 172 - 183, 更新 tests/unit/test_live2d_peek_static.py 中相关断言:围绕 getLive2DPeekViewport 提取精准的 viewport_source,并将 viewportW、canvasW、validVw、niriVirtualViewport 等函数体内实现断言迁移到该变量上;不要继续使用 _edge_peek_source() 的宽泛片段断言这些内容。static/app/app-ui/return-transitions.js (1)
421-425: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value这个参数名会骗人喵~建议补一句注释
coordinateSpace指的是入参 rect 所处的坐标空间,不是"想要的输出空间"。所以'virtual'反而不转换、'client'才转换——第一眼读过去很容易读反哦,笨蛋♪ 虽然getActiveModelTransitionRect那边有注释,但函数自身裸奔着呢喵。♻️ 加个注释就好啦
+ // `coordinateSpace` describes the SOURCE space of `rect`: + // 'virtual' -> already in virtual body coordinates, normalize only + // 'client' -> a DOMRect local to the physically cropped viewport function getNekoTransitionRect(rect, coordinateSpace = 'client') { return coordinateSpace === 'virtual' ? normalizeNekoScreenRect(rect) : I.toNekoVirtualTransitionRect(rect); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/app/app-ui/return-transitions.js` around lines 421 - 425, 在 getNekoTransitionRect 函数声明处补充注释,明确说明 coordinateSpace 表示传入 rect 的坐标空间而非目标输出空间,并注明 virtual 输入保持不转换、client 输入会转换为 virtual 坐标。static/avatar/avatar-ui-buttons/methods-return.js (1)
636-642: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win这个 early return 骑在清理逻辑前面,有点危险哦喵
第 642 行
if (!dragActivityFacts) return;一旦命中,下面这些全都不会执行:
- 第 655 行
container.setAttribute('data-dragging', 'false')- 第 675-679 行 解除
data-neko-return-click-suppressed而这两个属性正是
returnBtn的 click handler(第 196-206 行)用来拦截点击的。万一哪天多出一条让finishDragActivity返回null的路径,返回球就会永久点不动——猫娘被困在拖拽态里出不来,好可怜的喵~目前的调用序列里第一次调用一定会走完清理,所以还没真的翻车。但把"状态清理"和"埋点上报"绑在同一个 early return 上,本来就是脆的说。建议拆开:
♻️ 让状态清理无条件执行
const finishDragState = (moved, safetyToken, suppressClick = moved) => { if (safetyToken !== dragSafetyToken) return; dragUsesGlobalCursor = false; clearDragReleasePending(); clearDragCropHoldPending(); const dragActivityFacts = finishDragActivity(safetyToken); - if (!dragActivityFacts) return; + // Always release the drag attributes, even when the activity was + // already reported. Leaving data-dragging/click-suppressed set + // permanently disables the return ball. + if (!dragActivityFacts) { + container.setAttribute('data-dragging', 'false'); + setReturnClickSuppressed(false); + return; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/methods-return.js` around lines 636 - 642, 调整 finishDragState 中对 finishDragActivity 返回值的处理,移除其在状态清理逻辑之前的 early return;无论 dragActivityFacts 是否为空,都必须继续执行 data-dragging 重置及解除 data-neko-return-click-suppressed 的清理,仅在存在有效 dragActivityFacts 时执行后续埋点相关处理。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@static/app/app-ui/surface-floating-controls.js`:
- Around line 1548-1558: 更新欢迎提示调用处,不要将 window.showStatusToast
提取为裸函数引用;校验其是否为函数,并通过保持所属对象上下文的调用方式触发 Toast。若替换值不是函数,则安全降级到
I.showStatusToast,同时避免因无效 replacement 抛出异常并中断后续流程。
In `@static/avatar/avatar-ui-buttons/core.js`:
- Around line 111-147: Update _getNekoDesktopVirtualRect so its fallback
coordinates use the crop API’s offsetX/offsetY, matching
_getNekoDesktopVirtualViewportOrigin when toVirtualRect is unavailable or
throws. Apply the offsets to the original left/top before constructing the
returned rectangle, while preserving the existing virtual coordinates when
conversion succeeds.
In `@static/avatar/avatar-ui-buttons/idle-drag-and-subactions.js`:
- Around line 614-616: 三处边缘窥视逻辑使用了物理视口尺寸夹取虚拟坐标,需要统一改用虚拟视口尺寸。更新
static/avatar/avatar-ui-buttons/idle-drag-and-subactions.js 的
_reclampNekoIdleCat1EdgePeekToViewport(614-616)、_restoreNekoIdleCat1EdgePeekBeforeDrag(647-649)和
_clearNekoIdleCat1EdgePeekForTierExit(669-671),让各处 viewportW/viewportH 基于现有的
_getNekoDesktopVirtualViewportSize() 获取,并保持现有夹取逻辑不变。
In `@static/avatar/avatar-ui-buttons/methods-return.js`:
- Around line 1010-1030: Update flushPoint so the drag-release cleanup runs even
when point is unusable: perform handleMove only after isUsableDragPoint(point)
succeeds, but keep the dragReleasePending finalization block independent of that
check. Preserve the existing safety-token, crop-readiness, and state-reset
behavior.
- Around line 11-33: Update _getNekoIdleReturnDragGlobalScreenPoint so cursor
points containing only {x, y}, including values from
window.electronScreen.getCursorPoint(), are treated as global screen coordinates
and returned unchanged; do not add cropBounds.x/y in that path. Apply crop
offset only when the input explicitly represents local coordinates, preserving
the existing validation and screenX/screenY handling.
In `@static/live2d/live2d-ui-buttons.js`:
- Around line 833-839: 更新浮动按钮初始化的两个延迟回调,在写入 buttonsContainer.style.display 前检查
isLive2DPeekActive();若 Peek 已激活,跳过会覆盖隐藏状态的显示写入,并通过
_setLive2DPeekControlsSuppressed(true) 持续保持控件抑制,未激活时保留现有初始化行为。
In `@tests/unit/test_avatar_return_button_idle_tiers_static.py`:
- Around line 1929-1941: 强化
test_return_button_crop_ack_listener_is_removed_on_rebuild_and_cleanup 的断言,使其验证
window.removeEventListener 调用同时绑定 'neko:niri-pet-physical-crop-state-applied' 和
this._returnButtonDragHandlers.cropStateApplied,而不是仅检查文件中存在任意移除监听器调用;可针对相关移除逻辑块断言完整调用片段,并保留
setup 与 cleanup 两处的覆盖。
---
Outside diff comments:
In `@static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js`:
- Around line 1270-1295: 为可能返回 null 的虚拟元素矩形补充空值保护:在 _stepNekoIdleCat1Walk 的
static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js:1270-1295 中,取得
rect 后若为空则取消本次 journey 并立即返回,避免后续坐标解引用。在同文件:1317-1322 的 _startNekoIdleCat1Walk
中,为 currentRect 恢复空值分支,在调用 _resolveNekoIdleCat1TargetFacing 和
_beginNekoIdleCat1WalkActivity 前处理 null 并退出。
In `@static/avatar/avatar-ui-buttons/methods-return.js`:
- Around line 853-885: Move the `dragSafetyToken = safetyToken` assignment
immediately after computing `safetyToken`, before `startDragActivity`,
`_restoreNekoIdleCat1EdgePeekBeforeDrag`, or
`_dispatchNekoIdleReturnBallManualMove` can synchronously trigger cancellation.
Remove the later assignment while preserving the existing drag initialization
and token values.
---
Nitpick comments:
In `@static/app/app-ui/return-transitions.js`:
- Around line 421-425: 在 getNekoTransitionRect 函数声明处补充注释,明确说明 coordinateSpace
表示传入 rect 的坐标空间而非目标输出空间,并注明 virtual 输入保持不转换、client 输入会转换为 virtual 坐标。
In `@static/avatar/avatar-ui-buttons/methods-return.js`:
- Around line 636-642: 调整 finishDragState 中对 finishDragActivity
返回值的处理,移除其在状态清理逻辑之前的 early return;无论 dragActivityFacts 是否为空,都必须继续执行
data-dragging 重置及解除 data-neko-return-click-suppressed 的清理,仅在存在有效
dragActivityFacts 时执行后续埋点相关处理。
In `@tests/unit/avatar_idle_niri_crop_origin.test.js`:
- Around line 197-220: 收紧 avatar idle return drag 契约测试:先依据
handleMove、cropStateApplied 等起止标记从 methods-return.js 提取各自目标代码片段,再在对应片段内断言
dragSessionId、拖拽尺寸、抓取偏移和释放流程。避免使用可跨越任意函数边界的全文件 [\s\S]*? 匹配,同时保留现有断言覆盖的行为。
- Around line 9-23: Update readFunction to extract function boundaries robustly
instead of relying on the first “) {” and raw brace counting, accounting for
nested parameter expressions and braces inside strings, templates, and comments.
At minimum, validate the extracted source with new Function before returning it;
prefer structured parsing with an existing parser if available. Preserve the
current missing-function and unterminated-function assertions.
In `@tests/unit/live2d_peek_behavior.test.js`:
- Around line 101-126: 增强 createInlineStyle,使 display 和 pointerEvents 的直接属性读写与
setProperty/getPropertyValue 共用 values Map;添加对应 getter 和 setter,并保持现有优先级与
removeProperty 行为不变,确保恢复路径中的直接赋值能被断言捕获。
In `@tests/unit/test_avatar_return_button_idle_tiers_static.py`:
- Around line 2596-2601: Update the _source_slice_between call for the “return
button verified crop state handler” so its ending anchor no longer hardcodes 12
spaces of indentation. Use an indentation-insensitive anchor based on the next
known property or following statement, while preserving extraction of the
cropStateApplied handler.
- Around line 3769-3780: Move the welcome-back toast source-order assertions out
of test_cat1_walk_is_blocked_while_return_ball_drag_is_active_or_pending and
into a dedicated test_desktop_welcome_back_toast_routes_through_host_bridge().
Keep the existing floating_controls_source checks unchanged within the new test,
leaving the cat1 drag-blocking test focused only on preventing cat1 movement.
In `@tests/unit/test_live2d_peek_static.py`:
- Around line 172-183: 更新 tests/unit/test_live2d_peek_static.py 中相关断言:围绕
getLive2DPeekViewport 提取精准的 viewport_source,并将
viewportW、canvasW、validVw、niriVirtualViewport 等函数体内实现断言迁移到该变量上;不要继续使用
_edge_peek_source() 的宽泛片段断言这些内容。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5f90dfef-533c-43f4-9221-1ef9cfb6fca2
📒 Files selected for processing (18)
static/app/app-ui/return-transitions.jsstatic/app/app-ui/return-window-drag.jsstatic/app/app-ui/surface-floating-controls.jsstatic/avatar/avatar-ui-buttons/core.jsstatic/avatar/avatar-ui-buttons/idle-drag-and-subactions.jsstatic/avatar/avatar-ui-buttons/idle-journey-and-presentation.jsstatic/avatar/avatar-ui-buttons/idle-playground.jsstatic/avatar/avatar-ui-buttons/methods-return.jsstatic/avatar/avatar-ui-buttons/methods-setup.jsstatic/avatar/avatar-ui-buttons/methods-state-and-cleanup.jsstatic/live2d/live2d-core.jsstatic/live2d/live2d-interaction.jsstatic/live2d/live2d-ui-buttons.jstests/unit/avatar_idle_niri_crop_origin.test.jstests/unit/live2d_peek_behavior.test.jstests/unit/test_avatar_return_button_idle_tiers_static.pytests/unit/test_live2d_peek_static.pytests/unit/test_react_chat_idle_dock_static.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
Greptile Summary本次修订统一了 Niri 物理裁剪下的虚拟视口坐标,并补强返回猫咪拖动与 Live2D Peek 生命周期喵。
Confidence Score: 5/5此 PR 看起来可以安全合并,现有代码已处理此前线程指出的坐标混用、裁剪确认和拖动收尾问题喵。 当前 HEAD 未发现仍然存在的阻断性故障喵。
|
| Filename | Overview |
|---|---|
| static/avatar/avatar-ui-buttons/methods-return.js | 返回猫咪拖动现在通过连续虚拟坐标、裁剪确认等待和延迟释放处理 Niri 的瞬态载体切换喵。 |
| static/avatar/avatar-ui-buttons/core.js | 新增虚拟视口原点、尺寸及 DOM 矩形转换助手,并在裁剪关闭时正确回退物理窗口坐标喵。 |
| static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js | 闲置跟随、配对移动和 compact surface 目标统一到虚拟桌面坐标空间喵。 |
| static/avatar/avatar-ui-buttons/idle-drag-and-subactions.js | 边缘预览恢复、重夹取和 compact 跟随改用虚拟视口边界与虚拟元素矩形喵。 |
| static/app/app-ui/return-transitions.js | 返回过渡根据各模型管理器的坐标契约执行单次转换,并使用虚拟视口定位喵。 |
| static/live2d/live2d-interaction.js | Live2D Peek 的坐标和生命周期适配 Niri 裁剪原点变化及瞬态输入状态喵。 |
| tests/unit/avatar_idle_niri_crop_origin.test.js | 覆盖虚拟原点、坐标单次转换、拖动连续性、裁剪确认和超时收尾的行为回归喵。 |
| tests/unit/live2d_peek_behavior.test.js | 覆盖 Live2D Peek 在 Niri 裁剪与控件抑制场景中的状态同步和恢复喵。 |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Pointer down on return pet] --> B[Capture virtual and local anchors]
B --> C{Full Niri drag carrier ready?}
C -- No --> D[Hold latest continuous virtual point]
D --> E[Crop state applied or safety timeout]
C -- Yes --> F[Apply virtual position]
E --> F
F --> G{Release observed?}
G -- No --> H[Continue drag or stale-session cleanup]
G -- Yes --> I[Finalize drag and edge peek]
Reviews (22): Last reviewed commit: "test(niri): tighten blur guard contract" | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dbc248a273
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
CodeRabbit review follow-up is complete in Accepted and fixed:
Not applied: treating every cursor payload containing only Validation: Node behavior tests 37/37; focused Core static contracts 79/79. All seven inline threads have direct replies and are resolved. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
static/live2d/live2d-ui-buttons.js (1)
767-792: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win教程保护定时器仍可能解除 Peek 控件抑制喵
这几处检查只覆盖两个延迟回调和 ready 事件,但同一生命周期中的
tutorialProtectionTimer仍会在教程模式下每 300ms 写入display:flex!important。当教程与 Peek 同时激活时,它会重新显示本应隐藏的浮动控件喵。请在
tutorialProtectionTimer回调入口复用相同的 Peek 检查,并在抑制期间直接返回喵。🐾 建议修复喵
this.tutorialProtectionTimer = setInterval(() => { + if ( + typeof this.isLive2DPeekActive === 'function' + && this.isLive2DPeekActive() + ) { + if (typeof this._setLive2DPeekControlsSuppressed === 'function') { + this._setLive2DPeekControlsSuppressed(true); + } + return; + } + if (window.isInTutorial === true) {Also applies to: 851-857
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/live2d/live2d-ui-buttons.js` around lines 767 - 792, Update the tutorialProtectionTimer callback to perform the same isLive2DPeekActive check used by the surrounding button-display logic. When Peek is active, invoke _setLive2DPeekControlsSuppressed(true) when available and return before writing display:flex!important; preserve the existing tutorial protection behavior for non-Peek states.
🧹 Nitpick comments (1)
tests/unit/avatar_idle_niri_crop_origin.test.js (1)
20-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win避免
new Function持续触发 SAST 错误喵这里的源码来自仓库文件,且构造器不会执行函数体,因此不是外部输入可直接触发的 RCE 喵。
不过 OpenGrep 已将 Line 23 标为 ERROR;请改用只编译、不运行的vm.Script来保留语法校验喵。建议修改喵
- () => new Function(`${extracted}\nreturn ${name};`), + () => new vm.Script(`${extracted}\nvoid ${name};`),请确认该 OpenGrep 规则是否是合并门禁喵。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/avatar_idle_niri_crop_origin.test.js` around lines 20 - 26, 将提取函数源码的语法校验从 `new Function` 改为只编译、不执行的 `vm.Script`,以避免 OpenGrep SAST 报错。更新 `depth === 0` 分支中的 `assert.doesNotThrow`,继续校验 `${extracted}` 的语法并保留现有错误信息;如需确认门禁状态,再检查项目的 OpenGrep/CI 配置。Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@static/live2d/live2d-ui-buttons.js`:
- Around line 767-792: Update the tutorialProtectionTimer callback to perform
the same isLive2DPeekActive check used by the surrounding button-display logic.
When Peek is active, invoke _setLive2DPeekControlsSuppressed(true) when
available and return before writing display:flex!important; preserve the
existing tutorial protection behavior for non-Peek states.
---
Nitpick comments:
In `@tests/unit/avatar_idle_niri_crop_origin.test.js`:
- Around line 20-26: 将提取函数源码的语法校验从 `new Function` 改为只编译、不执行的 `vm.Script`,以避免
OpenGrep SAST 报错。更新 `depth === 0` 分支中的 `assert.doesNotThrow`,继续校验 `${extracted}`
的语法并保留现有错误信息;如需确认门禁状态,再检查项目的 OpenGrep/CI 配置。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e01df1d9-102a-4e6b-99e1-572b51df3fb3
📒 Files selected for processing (11)
static/app/app-ui/return-transitions.jsstatic/app/app-ui/surface-floating-controls.jsstatic/avatar/avatar-ui-buttons/core.jsstatic/avatar/avatar-ui-buttons/idle-drag-and-subactions.jsstatic/avatar/avatar-ui-buttons/idle-journey-and-presentation.jsstatic/avatar/avatar-ui-buttons/methods-return.jsstatic/live2d/live2d-ui-buttons.jstests/unit/avatar_idle_niri_crop_origin.test.jstests/unit/live2d_peek_behavior.test.jstests/unit/test_avatar_return_button_idle_tiers_static.pytests/unit/test_live2d_peek_static.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual) → reviewed against open PR#384fix/peek-niri-state-20260727instead of the default branch
🚧 Files skipped from review as they are similar to previous changes (7)
- static/app/app-ui/surface-floating-controls.js
- static/avatar/avatar-ui-buttons/core.js
- tests/unit/live2d_peek_behavior.test.js
- static/avatar/avatar-ui-buttons/idle-drag-and-subactions.js
- static/avatar/avatar-ui-buttons/idle-journey-and-presentation.js
- static/avatar/avatar-ui-buttons/methods-return.js
- tests/unit/test_avatar_return_button_idle_tiers_static.py
|
无法触碰pr。下次请允许maintainer协助修改pr |
合并最新 upstream/main,保留新的 Stealth Mode、跨屏恢复与可交互区域逻辑,同时保留 Niri 虚拟视口和 Peek 控件抑制行为。
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
static/live2d/live2d-interaction.js (1)
649-653: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win请修正虚拟视口锚点并清理隐藏定时器喵
- Line 649-653、Line 1070-1078:
visibleBounds.centerY是虚拟坐标,当前直接除以viewport.height,遗漏了viewport.top。当 Niri/多屏视口原点非 0 时,比例会被抬高并被clamp到 1,导致 peek 重算或跨显示恢复跳到错误的垂直位置。请统一改为(visibleBounds.centerY - viewport.top) / viewport.height喵。- Line 1886-1889: peek 激活时提前返回,跳过下方已有的
_hideButtonsTimer清理;已有的隐藏回调可能在抑制快照后继续覆盖按钮样式,退出 peek 时造成控件无法恢复。请先清除并置空该定时器,再执行抑制逻辑喵。建议修复喵
- visibleBounds.centerY / viewport.height, + (visibleBounds.centerY - viewport.top) / viewport.height, - : visibleBounds.centerY / viewport.height, + : (visibleBounds.centerY - viewport.top) / viewport.height, if (this.isLive2DPeekActive()) { + if (this._hideButtonsTimer) { + clearTimeout(this._hideButtonsTimer); + this._hideButtonsTimer = null; + } this._setLive2DPeekControlsSuppressed(true); return; }Based on learnings:本仓库的 JavaScript 审查偏好将多个问题集中到一条合并评论中喵。
Also applies to: 1070-1078, 1886-1889
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/live2d/live2d-interaction.js` around lines 649 - 653, 修正 Live2D peek 锚点计算,在 edgeAnchorRatio 及对应的垂直锚点计算中先减去 viewport.top,再除以 viewport.height,确保使用相对虚拟视口的坐标;同时在 peek 激活逻辑提前返回前清除并置空 _hideButtonsTimer,再执行按钮抑制逻辑。Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@static/live2d/live2d-interaction.js`:
- Around line 649-653: 修正 Live2D peek 锚点计算,在 edgeAnchorRatio 及对应的垂直锚点计算中先减去
viewport.top,再除以 viewport.height,确保使用相对虚拟视口的坐标;同时在 peek 激活逻辑提前返回前清除并置空
_hideButtonsTimer,再执行按钮抑制逻辑。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1428d148-4ad7-4b6b-96d3-26b847984e95
📒 Files selected for processing (4)
static/live2d/live2d-core.jsstatic/live2d/live2d-interaction.jstests/unit/live2d_peek_behavior.test.jstests/unit/test_live2d_peek_static.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual) → reviewed against open PR#384fix/peek-niri-state-20260727instead of the default branch
🚧 Files skipped from review as they are similar to previous changes (3)
- tests/unit/test_live2d_peek_static.py
- static/live2d/live2d-core.js
- tests/unit/live2d_peek_behavior.test.js
|
仅在 Niri Pet 物理裁剪启用时使用虚拟桌面尺寸,避免裁剪关闭后残留状态继续影响 CAT1 拖拽、跟随与边界限制。\n\n新增裁剪桥存在但 enabled=false 时回退当前窗口尺寸的回归测试。
同步上游 main 的最新改动,并保留 PR Project-N-E-K-O#2520 的 Niri Pet 拖拽、裁剪与 Live2D Peek 修复。
仅在 Niri Pet 物理裁剪启用且虚拟边界真实存在时使用虚拟原点,避免裁剪关闭后的残留状态与当前窗口尺寸形成混合坐标系。\n\n新增裁剪关闭时桌面 compact surface 回退当前窗口原点的行为回归测试。
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@static/avatar/avatar-ui-buttons/core.js`:
- Around line 75-88: Prevent null or undefined coordinates from being converted
to zero. In the coordinate resolution logic around virtualBounds and cropState,
validate virtualBounds.x/y and offsetX/offsetY are non-null before applying
Number.isFinite; apply the same non-null validation to fallback offsets and
virtualRect.x/y in static/avatar/avatar-ui-buttons/core.js lines 135-156,
preserving NaN so the existing screen/offset fallback is used when values are
not ready.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 326f6f56-45b2-479b-a350-24b6be662448
📒 Files selected for processing (2)
static/avatar/avatar-ui-buttons/core.jstests/unit/avatar_idle_niri_crop_origin.test.js
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
在虚拟原点、裁剪偏移和虚拟矩形转换中显式拒绝 null/undefined 坐标,保留 NaN 以触发现有 screen/offset 回退,避免过渡期坐标跳到零点或形成混合轴。\n\n新增虚拟原点、部分缺失 offset 和逐轴转换回退的行为测试。
同步上游 main 的最新改动,并保留 PR Project-N-E-K-O#2520 的 Niri Pet 拖拽、裁剪与 Live2D Peek 修复。
当返回猫咪拖拽已越过移动阈值但 Niri 裁剪确认超时时,按已完成拖拽收尾而不是误派发取消事件;保留现有未验证坐标保护。\n\n同步更新 Node 行为契约和 Python 静态契约。
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
static/avatar/avatar-ui-buttons/methods-return.js (1)
849-860: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win修复两个异常返回路径,别让拖拽状态卡死喵。
- 坐标不可用时会永久抑制点击(Line [849]–[860])。
setReturnClickSuppressed(true)在isUsableDragPoint(point)之前执行;校验失败后isDragging仍为false,后续清理路径不会运行,返回球的 click 会一直被拦截,直到页面重载喵。请把抑制设置移动到坐标校验成功之后喵。- 延迟释放期间的 blur 会误取消拖拽(Line [913]–[937]、Line [1010]–[1020])。
handleEnd设置dragReleasePending后清空了dragPointerType;native Wayland 下shouldIgnoreMissingMouseButtons()因而返回 false,随后windowBlur调用cancelDragState()并清掉 600ms release timer,最终发送 cancel 而不是完成拖拽喵。请直接把dragReleasePending纳入 blur 保活条件,或延后清空dragPointerType喵。建议修复喵
clearDragReleasePending(); clearDragCropHoldPending(); - setReturnClickSuppressed(true); const point = startPoint || getDragPoint(sourceEvent, clientX, clientY); if (!isUsableDragPoint(point)) return; + setReturnClickSuppressed(true);if (isDragging && - (shouldUseGlobalCursorForMouseDrag() || + (dragReleasePending || + shouldUseGlobalCursorForMouseDrag() || (dragActiveDispatched && shouldIgnoreMissingMouseButtons()))) { return; }Based on learnings:本仓库的 JavaScript 评审偏好将同一 PR 的相关问题集中列在一条评论中喵。
Also applies to: 913-937, 1010-1020
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/methods-return.js` around lines 849 - 860, 在 handleStart 中将 setReturnClickSuppressed(true) 移到 isUsableDragPoint(point) 校验成功之后,确保坐标无效时不会遗留点击抑制状态;同时更新 handleEnd 与 windowBlur 的拖拽保活判断,使 dragReleasePending 即使 dragPointerType 已清空也能阻止 blur 调用 cancelDragState(),保留延迟释放定时器并最终完成拖拽。Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@static/avatar/avatar-ui-buttons/methods-return.js`:
- Around line 849-860: 在 handleStart 中将 setReturnClickSuppressed(true) 移到
isUsableDragPoint(point) 校验成功之后,确保坐标无效时不会遗留点击抑制状态;同时更新 handleEnd 与 windowBlur
的拖拽保活判断,使 dragReleasePending 即使 dragPointerType 已清空也能阻止 blur 调用
cancelDragState(),保留延迟释放定时器并最终完成拖拽。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 50008e12-0d3d-41a7-b0fe-3fc9bf0c63f0
📒 Files selected for processing (3)
static/avatar/avatar-ui-buttons/methods-return.jstests/unit/avatar_idle_niri_crop_origin.test.jstests/unit/test_avatar_return_button_idle_tiers_static.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
同步上游 main 的最新改动,并保留 PR Project-N-E-K-O#2520 的 Niri Pet 拖拽、裁剪与 Live2D Peek 修复。
仅在拖拽起始坐标验证成功后启用点击抑制,并在延迟释放期间让 blur 保持拖拽会话存活,避免返回球永久不可点击或真实拖拽被误取消。\n\n同步更新 Node 与 Python 拖拽状态机契约。
|
已处理 CodeRabbit review #4814926730 的两条汇总意见,修复提交为 ce7a181:1)点击抑制现在只在拖拽起始坐标验证成功后启用,坐标无效时不会让返回球永久不可点击;2)dragReleasePending 已纳入 window blur 保活条件,延迟释放期间不会因 pointer type 已清空而误走 cancel。Node 71/71、Python 80/80 通过。分支同时已合入最新 main,合并过程无冲突。 |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
static/avatar/avatar-ui-buttons/methods-return.js (1)
925-927: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win延迟释放期间请冻结后续输入喵
Line [925] keeps
isDraggingactive whiledragReleasePendingis true, but Lines [984]-[1006] still allowmouseMove/touchMoveto callhandleMove()喵。由于 Line [926] 清空了dragPointerType,释放后的mousemove不会再被e.buttons === 0分支终止,可能改写最终位置,让 600ms 延迟释放落在 mouseup 之后的新坐标上喵。请在两个输入处理器进入handleMove()前跳过dragReleasePending,同时保留cropStateApplied.flushPoint()对最终排队点的调用喵。🛠️ 建议修复喵
mouseMove: (e) => { if (!isDragging) return; + if (dragReleasePending) return; touchMove: (e) => { - if (isDragging && e.touches && e.touches[0]) { + if (isDragging && !dragReleasePending && e.touches && e.touches[0]) {Also applies to: 984-1006
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/methods-return.js` around lines 925 - 927, 在 mouseMove 和 touchMove 处理器调用 handleMove() 之前检查 dragReleasePending,延迟释放期间直接跳过后续输入,避免改写最终位置;同时保留现有的 cropStateApplied.flushPoint() 调用,以应用最终排队的坐标点。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@static/avatar/avatar-ui-buttons/methods-return.js`:
- Around line 925-927: 在 mouseMove 和 touchMove 处理器调用 handleMove() 之前检查
dragReleasePending,延迟释放期间直接跳过后续输入,避免改写最终位置;同时保留现有的 cropStateApplied.flushPoint()
调用,以应用最终排队的坐标点。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 99b5330e-9a0a-48b7-9f85-9ae361f9b7a9
📒 Files selected for processing (3)
static/avatar/avatar-ui-buttons/methods-return.jstests/unit/avatar_idle_niri_crop_origin.test.jstests/unit/test_avatar_return_button_idle_tiers_static.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/unit/avatar_idle_niri_crop_origin.test.js
- tests/unit/test_avatar_return_button_idle_tiers_static.py
同步上游 main 的最新改动,并保留 PR Project-N-E-K-O#2520 的 Niri Pet 拖拽、裁剪与 Live2D Peek 修复。
同步最新 upstream/main,保持 Niri 拖拽与 Peek 修复分支可合并。
Niri 扩展完整拖拽载体期间,直接以 dragCropHoldPending 保持拖拽会话,避免窗口短暂失焦误触发取消。同步更新 Node 与 Python 状态机契约。
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
static/avatar/avatar-ui-buttons/methods-return.js (2)
949-951: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift防止旧拖拽的结束事件被新会话吞掉喵
这里把
isDragging设为false后延迟两帧调用finishDragState();在这两帧内用户可以重新触发handleStart(),它会递增dragSafetyToken,于是旧回调命中safetyToken !== dragSafetyToken直接返回,旧会话永远不会发送return-ball-drag-end。这会让 preload 侧基于dragSessionId的原生拖拽释放状态滞留喵。请在允许新拖拽前完成旧会话的终止(例如增加 terminalizing 标记并让
handleStart()等待,或保存旧会话快照并确保旧会话先发送结束事件)喵。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/methods-return.js` around lines 949 - 951, Ensure the delayed completion around finishDragState does not get invalidated by a new drag session: complete the previous session’s termination and emit its return-ball-drag-end event before handleStart permits a new session, using a terminalizing guard or an equivalent saved-session approach while preserving dragSafetyToken validation for unrelated stale callbacks.Source: Linked repositories
689-708: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win裁剪等待期间仍会被 5 秒安全计时器提前取消喵
当
dragCropHoldPending为true时,finishAsMoved被设为false,因此不会进入ACTIVE_DRAG_STALE_MS的 30 秒不活动判断;原本 5 秒安全计时器触发后会直接取消仍在等待 Niri 裁剪确认的有效拖拽。请让缺失结束事件的超时判断也覆盖 crop hold,并在真正 30 秒无活动后再结束或取消会话;若队列中的 pending 点代表用户仍在移动,也应刷新lastMovedAt喵。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar/avatar-ui-buttons/methods-return.js` around lines 689 - 708, Update resetDragStateAfterMissingEnd so dragCropHoldPending sessions also undergo the ACTIVE_DRAG_STALE_MS inactivity check instead of being ended by the 5-second safety timeout. Keep the session active and reschedule the safety check until 30 seconds have elapsed since the last activity, then finish or cancel it as appropriate. When queued pending points indicate continued pointer movement, refresh dragActivity.lastMovedAt before evaluating inactivity.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/unit/avatar_idle_niri_crop_origin.test.js`:
- Line 708: 收紧 avatar idle Niri crop origin 测试中的静态断言:先使用 readSection
截取目标处理段,再仅在该片段内验证同一 if 分支包含
isDragging、相关拖拽挂起/释放条件、shouldUseGlobalCursorForMouseDrag() 和 return,并断言随后调用
cancelDragState(),避免使用跨越无关源码的 [\s\S]*? 匹配。
---
Outside diff comments:
In `@static/avatar/avatar-ui-buttons/methods-return.js`:
- Around line 949-951: Ensure the delayed completion around finishDragState does
not get invalidated by a new drag session: complete the previous session’s
termination and emit its return-ball-drag-end event before handleStart permits a
new session, using a terminalizing guard or an equivalent saved-session approach
while preserving dragSafetyToken validation for unrelated stale callbacks.
- Around line 689-708: Update resetDragStateAfterMissingEnd so
dragCropHoldPending sessions also undergo the ACTIVE_DRAG_STALE_MS inactivity
check instead of being ended by the 5-second safety timeout. Keep the session
active and reschedule the safety check until 30 seconds have elapsed since the
last activity, then finish or cancel it as appropriate. When queued pending
points indicate continued pointer movement, refresh dragActivity.lastMovedAt
before evaluating inactivity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 06f5a7af-6abf-44e6-b1b1-51f82a187734
📒 Files selected for processing (3)
static/avatar/avatar-ui-buttons/methods-return.jstests/unit/avatar_idle_niri_crop_origin.test.jstests/unit/test_avatar_return_button_idle_tiers_static.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/unit/test_avatar_return_button_idle_tiers_static.py
将 windowBlur 断言限制为同一处理段内的精确 if/return/cancel 结构,避免宽泛正则掩盖状态机回归。
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0cbd29a91
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const virtualViewport = getNekoTransitionVirtualViewportSize(); | ||
| const viewportWidth = Math.max(width, virtualViewport.width); | ||
| const viewportHeight = Math.max(height, virtualViewport.height); |
There was a problem hiding this comment.
Keep native edge-peek drag restoration in virtual space
On Niri when native multi-window return-ball dragging is enabled, this now positions edge-anchored cats using the virtual viewport, but beginDrag in return-window-drag.js calls the separate I.restoreNekoIdleCat1EdgePeekBeforeDrag, which still reads a crop-local DOMRect and clamps against the physically cropped window.innerWidth/innerHeight. Starting a drag from a right/bottom edge peek therefore collapses the virtual position into the carrier bounds and saves that incorrect position. Fresh evidence beyond the resolved avatar-helper thread is this distinct native-drag path; its restore helper also needs the virtual viewport and virtualized rect.
Useful? React with 👍 / 👎.
| const cropState = cropApi && typeof cropApi.getState === 'function' | ||
| ? cropApi.getState() | ||
| : null; | ||
| const virtualBounds = cropState && cropState.virtualBounds; | ||
| const width = Number(virtualBounds && virtualBounds.width); |
There was a problem hiding this comment.
Require an enabled crop for transition viewport bounds
When the physical-crop bridge remains installed after reporting enabled: false, this helper still accepts stale virtualBounds. Its new consumers then position or clamp the return ball against the full virtual desktop instead of the current Pet window, potentially placing right/bottom edge anchors outside the visible window. Fresh evidence beyond the resolved core-helper thread is that this separate transition helper remains ungated; require cropState.enabled === true before using virtualBounds.
Useful? React with 👍 / 👎.
| const left = Number(Number.isFinite(Number(rect.left)) ? rect.left : rect.x); | ||
| const top = Number(Number.isFinite(Number(rect.top)) ? rect.top : rect.y); |
There was a problem hiding this comment.
Reject null transition coordinates before numeric coercion
When the crop bridge returns a partial conversion such as {x: null, y: 252, width, height}, Number(null) becomes 0, so this normalizer accepts the missing axis as a real origin coordinate. toNekoVirtualTransitionRect then treats that result as valid instead of falling back to the original rect, causing model/return transition anchors to jump to the left or top edge during a transient incomplete crop update. Check for null/undefined before coercing either the left/top fields or their x/y fallbacks.
Useful? React with 👍 / 👎.
| dragReleaseTimer = setTimeout(() => { | ||
| dragReleaseTimer = 0; | ||
| if (!dragReleasePending || safetyToken !== dragSafetyToken) return; | ||
| dragReleasePending = false; | ||
| isDragging = false; |
There was a problem hiding this comment.
Cancel pending drag-release work during manager cleanup
If cleanupFloatingButtons or a floating-button rebuild runs during this 600 ms Niri crop-release wait, cleanup removes the listeners and old container but cannot clear this closure-owned timer. The callback subsequently finishes the obsolete drag and dispatches return-ball-drag-end for the detached container; the desktop-state bridge can still classify it as visible from its retained attributes and publish a stale visible: true state with no rect, racing the newly created return ball and crop session. Expose a drag cancellation/cleanup handler and invoke it before discarding _returnButtonDragHandlers.
Useful? React with 👍 / 👎.
改动概述 / Summary
修复 Niri / Native Wayland 下猫咪与 Live2D Peek 交互中的坐标跳变和拖动中断问题。
根因是 Pet 窗口从猫咪尺寸裁剪载体切换到全工作区拖动载体时,Chromium 事件可能在旧局部坐标、裁剪虚拟坐标和屏幕坐标之间短暂切换;同时 Niri 在载体扩展期间可能短暂上报窗口 blur 或
buttons === 0。旧逻辑会把这些瞬态当作真实位移或鼠标释放,造成按下后固定距离跳位、无法继续拖动,或 Peek 状态提前结束。本 PR:
movementX/Y保持旧/新载体坐标切换期间的连续性。配套桌面宿主 PR:Project-N-E-K-O/N.E.K.O.-PC#384
回归报告 / Regression Report
不适用:未修改
app/、main_logic/或memory/下的 Python 文件。不拆分理由 / Why Not Split
不适用:计入上限的业务文件不超过 20 个。拖动、裁剪与 Peek 修改属于同一 Niri 坐标生命周期,测试随实现一并提交。
测试 / Testing
uv run pytest tests/unit/test_react_chat_idle_dock_static.py tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_live2d_peek_static.py -q— 75 passednpm test(frontend/react-neko-chat)— 432 passednpm run build(frontend/react-neko-chat)— 生产构建成功Summary by CodeRabbit