2026-08-10: 人生导图 v6.1 优化定版 — 9 个逻辑改动 / 35 处行级修改(?v=p3 已上线验证)

Decision: 对部署在 Cloudflare Workers 的单文件 web app「人生导图」(Life Map v6.1)做一轮以真实问题为导向的优化:不碰渲染引擎、不碰云同步协议、不动布局方向,只修确认的 bug + 可量化的性能浪费 + 体验硬伤。共 9 个逻辑改动、35 处行级修改python3 build.py 重建 dist/index.html(540 KB)后以 cache-bust 版本参数 ?v=p3 上线,线上验证通过。

背景(Context)

  • 应用:单文件 markmap 思维导图,根=幸福,身体/心理→左、财富→右(双向布局 patch);5 个 tab(导图/任务/知识库/人脉/规划);4 秒轮询 GET <URL>/api/state 云同步(rev 单调递增 + 防回显)。
  • 源码:D:\hermes\hsdesign_work\glm_lifemap_v61\life-map-v6.1\(WSL:/mnt/d/hermes/hsdesign_work/glm_lifemap_v61/life-map-v6.1/)。
  • 前置约束(来自 sozo-life-map SKILL.md 铁律):v3 markmap 渲染引擎不得回退;云同步协议(4s 轮询 / rev / parent 剥离 / 防回显)不得改;左侧镜像 patch 不得破坏;breadcrumb 视图守卫、closest('circle') 等已修 bug 不得复发。
  • 审查对象:app.js(2,210 行)+ style.css(1,309 行)+ index.html(19 KB)。

35 处改动分类总结

类别数量逻辑改动
性能优化5renderData 50ms 缓冲 · treeSignature 树指纹快速路径 · applySearchHighlight 单趟遍历 · 面包屑签名去重 · 云重载 getTree 剪枝
bug 修复3pruneDoneGoals 不再删数据(数据丢失)· pushCloud 成功才推进防回显 · 搜索框快捷键提示 ⌘K→⌘F
功能修复1展开/收起全部 折叠状态持久化(userState + 云端)

「35 处」= 9 个逻辑改动展开后的行级修改总数(含注释、守卫、调用点)。以下逐项说明为什么

关键改动与”为什么”

性能优化(5)

1. renderData 左侧镜像 patch 加 50ms 缓冲(app.js renderData patch)

  • 为什么:duration 已设为 0(关动画),但旧代码 (duration || 400)0 当 falsy 回退成 400 → 每次渲染都白等 450ms 才应用左柱镜像修正(foreignObject x / circle cx / line x),所有折叠/编辑/搜索跳转/云同步重载都被拖慢。改为 typeof duration === 'number' ? duration : 400,duration=0 时只需 50ms 缓冲(D3 零时长过渡一帧内结束)——修复了”关动画却仍然慢”的隐性矛盾

2. pullCloud 树签名快速路径(treeSignature / countNodes / arraysEqual)(app.js pullCloud

  • 为什么:4 秒轮询里,rev 变化 ≠ 树内容变化(其他设备只是折叠/展开、或模块数据推送也会 bump rev)。旧代码每次 rev 变化都 setData + renderData + fit:① 当前 pan/zoom 被重置;② 折叠状态是”本地优先”(userState merge),重载根本不会采纳远端折叠 → 纯浪费。新增:
    • treeSignature():DFS 前序把每个节点 content#pillar 拼成指纹(子节点间 \u0001 分隔);
    • countNodes():节点数快速比对(O(1) 剪枝,先比数量再比指纹);
    • 两者都相同 → 判定”树没变”,跳过整棵重载,只同步 rev。

3. 模块数据变化检测(arraysEqual)(app.js pullCloud 内)

  • 为什么:knowledge_nodes / contacts / goals 每 4 秒无条件替换引用 → 下游模块视图整块重渲染。改为 arraysEqual(长度 + JSON.stringify 比对)只有真变化才采纳。轮询结果分三态:'tree'(重载导图)/ 'meta'(只刷模块视图,不打断缩放)/ false(什么都不做)。

4. updateBreadcrumb 内容签名去重(app.js updateBreadcrumb

  • 为什么:该函数被 setInterval(updateBreadcrumb, 1000) 每秒调用,但折叠状态不变时 99% 的调用内容都没变——旧代码无条件 innerHTML 全量替换 → 无谓布局/重绘。加 bc.dataset.sig 缓存 HTML 签名,没变直接 return。

5. 云同步重载统一走 getTree() 剪枝(app.js 轮询 handler)

  • 为什么:旧代码云重载直接 setData(STATE.cloudTree)30 天前 ✅ 完成的节点”复活”(prune 只在整页加载时跑过一次)。改为 setData(getTree()),与整页加载行为一致(30 天 ✅ 隐藏)。

bug 修复(3)

6. pruneDoneGoals 改为纯函数,不再污染源数据(app.js pruneDoneGoals

  • 本次最重要修复(数据丢失 bug):旧代码 STATE.goals = STATE.goals.filter(...) —— 每次 renderGoals()永久删除完成超过 30 天的目标,且会随 pushCloud 推上云端把数据洗掉。改为纯函数 pruneDoneGoals(items) 只过滤显示列表,源数据(STATE.goals / 云端)保留。

7. pushCloud 只有 PUT 成功才推进防回显(app.js pushCloud

  • 为什么:旧代码 PUT 无论成败都置 cloudRev → 推送失败后,后续轮询把远端新 rev 误判为”自己推的”而永不采纳(防回显失效),两端永久不同步。现在 res 成功才更新 cloudRev / lastSavedLocal,失败走 error 状态。

8. 搜索框快捷键提示 ⌘K → ⌘F(index.html)

  • 为什么:搜索实际绑定 ⌘F(⌘K 是快速记录),UI 提示与事实不符,误导用户。

功能修复(1)

9. 展开/收起全部 折叠状态持久化(app.js handleCtxAction

  • 为什么:旧代码只改内存 payload.fold 不持久化 → 刷新/换设备后全部丢失,与点圆圈折叠(wrapToggle 存 userState)行为不一致。现在同步写 STATE.userState[path] + saveUserState() + pushCloud(),折叠状态随云同步跨设备生效。

部署验证

  1. node --check app.js 语法通过;
  2. python3 build.py 重建 dist/index.html(540 KB);
  3. 12 项改动后置回归检查全 OK(renderData 缓冲 / pushCloud 成功检查 / treeSignature / countNodes / arraysEqual / poll getTree / breadcrumb 签名 / expand-all 持久化 / collapse-all 持久化 / pruneDoneGoals 非变异 / applySearchHighlight 单趟 / ⌘F 提示);
  4. 线上以 cache-bust 版本参数 ?v=p3 部署并验证通过。

可复用资产

  • 脚本:tree_signature.py(treeSignature/countNodes/arraysEqual 的独立 Python 移植 + 测试用例)→ 见 skills/prime-life-map-optimizations
  • 速查清单:skills/prime-life-map-optimizations/SKILL.md(本决策的维护速查版)
  • 2026-08-10 · decision-log
  • skills/prime-life-map-optimizations(tree_signature.py + SKILL.md 速查清单)
  • 源码:/mnt/d/hermes/hsdesign_work/glm_lifemap_v61/life-map-v6.1/(app.js 内每个改动点都有 FIX(...) 注释)