Skip to content

fix: 修复深度 review 确认的 4 个正确性/契约 bug - #11

Draft
sskyy wants to merge 4 commits into
mainfrom
cursor/fix-axle-fatal-bugs-7b83
Draft

fix: 修复深度 review 确认的 4 个正确性/契约 bug#11
sskyy wants to merge 4 commits into
mainfrom
cursor/fix-axle-fatal-bugs-7b83

Conversation

@sskyy

@sskyy sskyy commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

深度 review 中逐一复现确认的 4 个 bug 的修复,每个修复都有定向测试锚定(254 → 266 个测试,全绿)。

1. RxListHost:负索引 / 越界 splice 导致数据与画面静默分叉

data0 的 RxList.splice 委托原生 Array.prototype.splice(支持负 start 与越界 start),但 patch 里的 argv 是未归一化的原始参数。handleSplice 原样使用 argv[0]

  • splice(-1, 0, x)findAnchor(-1) 访问 hosts[-1] 抛 TypeError,被行级回滚吞掉降级为空行——数据里有该项、画布不渲染,只有一条 console.error;
  • 空列表上 pop() / shift() 会向 root.on('error') 派发一次框架内部错误(本应是 no-op)。

修复:按原生 splice 语义、基于 splice 前的 hosts.length 归一化 start

2. FunctionHost:结构路径渲染错误逃逸错误契约并泄漏场景图节点

renderSource 只捕获了 source() 求值的抛错;返回结构的渲染抛错(非法 child、未知标签、无钩子时组件 rethrow 等)完全没有防护:

  • 错误从 DeferredBindingEffect 的微任务里逃逸成未捕获异常,root.on('error') 收不到;
  • innerPlaceholder 已插入但 this.innerHost 未赋值,每次依赖触发重试都泄漏一个占位符和一棵半渲染子树(含未清理的 effect / 事件绑定)。

修复:复用 RxListHost.recoverFailedRow 的模式——清理半渲染 host 的绑定(parentHandle 模式)、回滚 (boundary, placeholder) 区间内已插入的节点、保持 effect 活跃(依赖恢复后该区域可重新渲染),再报告给 root error 钩子(无钩子时保持向上抛出,且抛出前已回滚干净)。

3. rxWindowedList:已删除条目最后才被卸载(优先级反转)

distanceTo 对已不在空间索引里的条目返回 -1,而卸载队列按距离降序消化——已删除条目被排到队尾,预算排队期间以"幽灵行"形态滞留在存活条目之后,与注释宣称的"排到队首优先卸载"正好相反。修复:缺失 bounds 视为无穷远。

4. insertBefore:锚点丢失时静默错位

锚点不在 parent.children 里时 indexOf 返回 -1,leafer 的 add(child, -1)splice(-1, 0) 解释为"插到倒数第二位"——簿记失配会静默扩散。修复:加 assert 响亮失败。

测试

  • test/rx-list.test.tsx:新增 splice 归一化 describe(负索引插入/删除/替换、双向越界 clamp、空列表 pop/shift 为静默 no-op);
  • test/function-host.test.tsx:新增结构路径错误的回滚/上报/恢复/反复失败不泄漏、无钩子时回滚后向上抛、回滚不误伤相邻 sibling 三个用例;
  • test/rx-windowed-list.test.ts:新增"索引删除的条目最先卸载"用例;
  • test/leafer-bridge.test.ts:新增锚点不在父级 children 时响亮失败的用例。

npm run check(typecheck + lint + 266 tests + build)全绿。

Open in Web Open in Cursor 

cursoragent and others added 4 commits July 9, 2026 05:48
…exes

data0's RxList.splice delegates to native Array.prototype.splice (which
accepts negative and out-of-range start), but forwards the raw argv in the
trigger info. RxListHost used argv[0] as-is, so findAnchor(negative) read
hosts[-1], crashed, and the new row was silently degraded to an empty row
(data and scene graph diverge); pop()/shift() on an empty list surfaced a
framework-internal error. Normalize start against the pre-splice host count
with native splice semantics before anchoring.

Co-authored-by: Zhenyu Hou <skyking_H@hotmail.com>
renderSource only routed source() evaluation errors to the root error hook;
errors thrown while rendering the returned structure (invalid child, unknown
tag, rethrowing component, ...) escaped as uncaught microtask exceptions and
leaked one placeholder plus a half-rendered subtree per retry.

Reuse the RxListHost row-recovery pattern: destroy the partial host's
bindings (parentHandle mode), remove every node inserted into the
(boundary, placeholder) interval, keep the effect alive so the region can
recover, then dispatch to root.on('error') or rethrow when no hook exists.

Co-authored-by: Zhenyu Hou <skyking_H@hotmail.com>
distanceTo returned -1 for entries no longer in the spatial index, but the
unmount queue drains by descending distance, so deleted entries sorted to
the back and lingered as ghost rows behind far-away survivors during budget
draining - the opposite of the documented priority. Treat missing bounds as
infinitely far so they unmount first.

Co-authored-by: Zhenyu Hou <skyking_H@hotmail.com>
If the anchor was missing from parent.children, indexOf returned -1 and
leafer's add(child, -1) spliced the node into the second-to-last slot -
silent misplacement that lets bookkeeping corruption spread. Fail loudly
instead.

Co-authored-by: Zhenyu Hou <skyking_H@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants