Releases: WinterMosquito/Obsidian-Mindmap-Studio
Release list
0.1.8
0.1.8 是一次性能模式可靠性与性能版本:修复性能模式下大型导图(数千节点)子树剪枝可能漏渲染可见节点的缺陷——节点坐标在布局之外被改写(自由拖拽、SET_NODE_CUSTOM_POSITION)后,过时的子树记账范围会把新位置上的可见节点剪掉;同时把平移 / 缩放 / 居中时的全树重绘开销从数十毫秒降到亚毫秒级。无破坏性变更(.mindmap.md 格式、命令 ID 与设置项均未变)。
修复
- 子树剪枝不再漏渲染自由拖拽后的可见节点:性能模式自底向上记账每个节点的子树纵向范围,渲染期据此整棵跳过视口外的子树(纵向可剪、横向不可剪——子节点在右侧展开,父节点出视口不代表子节点出视口)。但当坐标在布局之外被改写(自由拖拽、
SET_NODE_CUSTOM_POSITION只写数据不触发 render)时,已记账的__subLo/__subHi仍是旧数字,而topgetter 已返回新值 ⇒ 会「用旧范围剪掉新位置上的可见节点」。现新增失效钩子:布局之外改写坐标立即禁用剪枝,直到下一次布局重记账(refreshSubtreeExtents)再启用。关键判断是布局后一律启用——top的 gettercustomTop || _top已把自定义坐标计入top,读到的就是最终坐标,范围本就准;若改成「树内存在自定义坐标就禁用」,会让自由拖拽过一次的用户永久失去 -91% 的遍历收益(实测过,已修正) - 出视口节点不再残留:剪枝跳过整棵子树后,被跳过子树里会残留「上一轮物化、现已出视口」的节点——
removeSelf只把group从 DOM 摘下、不置空group(实测 5002 节点树有 3104 个此类脱离 DOM 的group对象)。现新增在屏节点集合renderer.attachedNodes,每轮渲染入口补做一次摘除核对(reconcileOutOfClientNodes),只遍历在屏集合,代价 O(在屏数) ≈ 数十项、不随树规模增长;摘除放在剪枝分支里会随命中次数重复扫描(实测每轮命中 76 次),故统一在渲染入口跑一次
性能
- 平移 / 缩放 / 居中重绘短路:
onViewDataChange的语义是「补挂新进入视口的节点、摘掉离开的」,但视口可见集合未变时(平移后仍落在原可见集)这轮全树分片渲染(render(..., async=true),每节点一个setTimeout)纯属浪费——而view_data_change在平移 / 缩放 / 居中时无条件 emit。现新增视口可见签名(含uid/left/top/width/height),相同即短路。实测 5001 节点:单次全树重绘 70.8ms → 0.6ms;节点尺寸变化(图片 / 数学公式定稿)与树结构变化(折叠 / 增删)都会使签名失效,故该优化不影响正确性 - 子树剪枝(见上「修复」):渲染遍历 -91%。实测 5002 节点真实形态:5002 次判定 → 46 个可见;成本分解显示其中约 60% 是遍历本身的每节点记账开销,故「把某一处判定变便宜」的微优化无效(如 svg.js
transform()memo 实测 97.7% 命中、零墙钟收益,已回退并记入vendor/BUILD.md「已否决的假设」)——唯一有效方向是少访问节点
工程
- 渲染契约可视化验证脚本新增剪枝覆盖率判据:
scripts/verify-visual.mjs现核对剪枝是否真的落在热路径上、触发次数与跳过规模是否与实测访问数量级一致(沿用补丁 5 的__MEASURE_STATS__诊断先例,pruneStats经window.__PRUNE_STATS__暴露) - 新增
scripts/gen-perf-fixture.mjs:生成大型导图性能测试 fixture,便于复现数千节点的剪枝 / 重绘基准 vendor/BUILD.md补丁 11 记录:子树剪枝设计、失效钩子判据、视口签名短路,以及已否决的假设(svg.js transform memo 等);tests/vendor-contract.test.ts同步引擎两处 sha256
兼容性说明
- 需要 Obsidian 1.13.0+,仅桌面端;
.mindmap.md格式、命令 ID 与设置项均未变更——纯修复 / 性能版本,无需迁移 - 本轮
styles.css无变化,更新只需替换main.js(发布资产仍建议三件套齐全) - 无新增设置项、无新增第三方依赖
Fixed
- Subtree pruning no longer drops visible nodes after free-drag: in performance mode each node's subtree vertical extent is tallied bottom-up after layout, and the renderer skips whole out-of-viewport subtrees (vertical only — children expand to the right, so a parent leaving the viewport does not imply its children do). But when coordinates are rewritten outside the layout pass (free-drag,
SET_NODE_CUSTOM_POSITIONwrites data without triggering a render), the tallied__subLo/__subHiare stale while thetopgetter already returns the new value — so the old extent would prune nodes that are now visible at the new position. An invalidation hook now disables pruning the moment coordinates are rewritten outside layout, and re-enables it on the next layout re-tally (refreshSubtreeExtents). The crucial decision is to re-enable after every layout: thetopgettercustomTop || _topalready folds the custom coordinate intotop, so the extent read at render time is correct. Disabling merely because "custom coordinates exist" would make a user who free-dragged permanently lose the -91% traversal win (verified, and fixed) - Out-of-viewport nodes no longer linger: after a whole subtree is pruned, nodes that were materialized in a previous round but are now off-screen would linger —
removeSelfonly detaches thegroupfrom the DOM and does not null it (measured 3104 such detachedgroupobjects in a 5002-node tree). A new on-screen setrenderer.attachedNodesis reconciled once at each render entry (reconcileOutOfClientNodes), iterating only the on-screen set for a cost of O(on-screen count) ≈ tens of items that does not grow with tree size; doing it inside the prune branch would rescan per hit (76 hits per round measured), so it runs once at the render entry instead
Performance
- Pan / zoom / center redraw short-circuit:
onViewDataChange's job is "attach nodes entering the viewport, detach those leaving", but when the visible set is unchanged (e.g. after a pan that stays within the same visible set) the whole-tree sliced render (render(..., async=true), onesetTimeoutper node) is pure waste — andview_data_changeemits unconditionally on pan / zoom / center. A viewport-visibility signature (withuid/left/top/width/height) now short-circuits identical rounds. Measured on 5001 nodes: a single full-tree redraw dropped from 70.8ms to 0.6ms; node-size changes (image / math-settling) and tree-structure changes (collapse / add / delete) invalidate the signature, so correctness is unaffected - Subtree pruning (see "Fixed" above): render traversal -91%. Measured on a 5002-node real-shape baseline: 5002 checks → 46 visible; cost breakdown showed ~60% is per-node bookkeeping overhead of the traversal itself, so "making one check cheaper" micro-opts are ineffective (e.g. an svg.js
transform()memo hit 97.7% but yielded zero wall-clock gain and was rolled back — recorded under "Rejected assumptions" invendor/BUILD.md) — the only effective direction is visiting fewer nodes
Engineering
- The visual render-contract verifier gained a pruning-coverage criterion:
scripts/verify-visual.mjsnow checks that pruning actually lands on the hot path and that fired-count / skipped-extent match the measured access magnitude (following patch 5's__MEASURE_STATS__precedent,pruneStatsexposed viawindow.__PRUNE_STATS__) - New
scripts/gen-perf-fixture.mjs: generates large mind-map performance fixtures to reproduce the thousands-of-nodes pruning / redraw baselines vendor/BUILD.mdpatch 11 records: the subtree-pruning design, the invalidation-hook criterion, the viewport-signature short-circuit, and rejected assumptions (svg.js transform memo, etc.);tests/vendor-contract.test.tssyncs the engine's two sha256 values
Compatibility notes
- Requires Obsidian 1.13.0+, desktop only; the
.mindmap.mdformat, command IDs and settings are unchanged — a fix/performance release with no migration needed styles.cssis unchanged in this release — updating onlymain.jsis sufficient (the full three-file release bundle is still recommended)- No new settings, no new third-party dependencies
0.1.7
0.1.7 是一次可靠性与工程门禁版本:修复「节点文本编辑失败毫无反馈」「工具栏图标尺寸兜底压掉主题变量通道」两处缺陷,补上两处此前从未被类型检查覆盖的真实类型错误;同时完成一轮性能去平方(数学公式重排的节点反查)与工程门禁收紧——三个发布相关配置纳入 tsc、依赖矩阵与文档清单纳入机械校验。无破坏性变更(.mindmap.md 格式、命令 ID 与设置项均未变)。
修复
- 节点文本编辑失败不再毫无反馈:在弹窗中编辑节点文本时,若底层操作失败,此前 Promise 拒绝被静默吞掉——用户点了「在弹窗中编辑」却毫无反应,也无法判断是自己操作有误还是插件出错;现在会弹出明确提示,且不触碰写回与保存通道(引擎状态、保存调度均保持原样,不会写入半成品)
- 工具栏图标尺寸兜底不再压掉主题变量通道:图标尺寸此前依赖官方内部类名消费
--icon-size变量;新增的自包含兜底规则 specificity 高于官方规则,若把尺寸写死就会连变量通道一起覆盖,导致「跟随官方尺寸体系 / 允许用户与主题覆盖」的设计失效。现走var(--icon-size, 16px):变量在位时跟随变量,官方改名致其不再消费时回落到 16px,两条失效路径都兜住 - 依赖矩阵的一处类型逃逸:
ModuleGroup联合类型遗漏了domain,而 9 个模块边界块中有 8 个把domain列为允许依赖——该声明此前完全逃过类型检查。已补入类型(并明确标注「禁止跨组导入」清单有意不含domain,否则会与依赖矩阵语义相反) - LICENSE 校验器的 8 处类型违规:
plain-text-parser.mjs此前不在类型检查视野内,其行级 token 形状是无约束的Record<string, unknown>;现已补上精确形状定义,token 字段名一并纳入门禁
性能
- 数学公式重排的节点反查从平方级降为线性:含行内公式的导图在公式定稿后需把每个公式元素反查回所属节点以重排尺寸,此前逐个元素各走一遍全树遍历(元素数 × 节点数);现改为一次遍历建「节点 → DOM 组」映射、逐元素沿祖先链查表,复杂度 O(节点数 + 元素数 × 祖先链深度)。查表未命中时逐个回落原全树扫描(宁可慢、不可错),覆盖引擎在建表后重渲染的情形
- 段落文本不再重复拼接:Markdown 解析时同一份段落内容此前拼接两遍(显示文本与解析期快照各一次),现复用同一字符串
工程
- 三个发布相关配置纳入类型门禁:
eslint.config.mts/stylelint.config.mjs/manifest.json现在参与build前置的类型检查(manifest.json的 JSON 语法错误在构建期即被拦下,不必等到发布扫描),并因此退役了 ESLint 的「默认工程」兜底配置 - 模块解析切到
bundler:旧选项在 TypeScript 6 起被标记弃用、7 将移除,编辑器会持续报错。实测对现有 400+ 处无扩展名导入零改动通过;同时明确不使用「忽略弃用警告」的静音开关——它在当前锁定的 TypeScript 版本下反而会报配置错误并真的阻断构建 - 文档清单校验覆盖子目录:
AGENTS.md的docs/清单此前只校验顶层文件,docs/agents/下的详录虽已登记却从不被检查——指向不存在的文件无人拦截。现已纳入 - 依赖与配置的显式声明:死代码检查补上项目文件边界(结论与原推断一致,零残留);写盘失败回调改用函数类型属性,类型层面即无隐含
this,日后传入未绑定方法引用仍会被拦下 AGENTS.md重构为索引 + 详录:主文件 1628 行 → 183 行,保留硬规则(Don't/Do 配对)、决策表、编号工作流、命令表与索引;112 条设计约定与架构/测试详录迁至docs/agents/四份文档。未登记的超限文件数只允许递减这一约束不变- 行数统计口径统一:此前文档登记值与实测的「差异」实为两套统计口径不同所致;现已统一为校验脚本的口径,并在文档中标注了不要用 PowerShell 统计行数
- 测试全量 60 文件 / 1820 例(0.1.6 为 58 / 1765)
- 文档:README 中英双语补齐快捷键表(搜索、撤销/重做、编辑、删除、适应画布、缩放到选区,含各自的让位条件);开发环境要求 Node 20+
兼容性说明
- 需要 Obsidian 1.13.0+,仅桌面端;
.mindmap.md格式、命令 ID 与设置项均未变更——纯修复 / 性能版本,无需迁移 - ⚠ 本轮
styles.css有变化(新增图标尺寸自包含兜底规则),更新时需同时替换main.js与styles.css,不能只换main.js - 无新增设置项、无新增第三方依赖
Fixed
- Editing node text no longer fails silently: when editing a node's text in the modal, a failed underlying operation used to be swallowed — the command appeared to do nothing and there was no way to tell whether the input was rejected or the plugin failed. It now shows an explicit error, and the write-back and save channels stay untouched (engine state and save scheduling are left alone, so no half-finished content is written)
- The toolbar icon-size fallback no longer overrides the theme variable channel: icon sizing previously relied on an Obsidian-internal class name to consume the
--icon-sizevariable. The new self-contained fallback rule is more specific than the official rule, so a hard-coded value would suppress the variable channel too — defeating the "follow the official sizing / let users and themes override it" design. The value is nowvar(--icon-size, 16px): it follows the variable when present, and falls back to 16px if the official class is renamed and stops consuming it, so both failure paths are covered - A type escape in the dependency matrix: the
ModuleGroupunion type was missingdomain, although 8 of the 9 module boundary blocks list it as an allowed dependency — the declaration was therefore never type-checked. The type is fixed (with an explicit note that the "forbidden cross-group import" list deliberately excludesdomain, which would otherwise invert the dependency matrix) - 8 type violations in the LICENSE checker:
plain-text-parser.mjswas outside the type-checked surface, so its line-token shape was an unconstrainedRecord<string, unknown>. A precise shape is now declared, which also brings the token field names under the gate
Performance
- Node lookups during math re-measurement go from quadratic to linear: after inline formulas settle, a mind map with formulas must map each formula element back to its owning node to re-measure layout. Each element previously triggered its own full-tree walk (elements × nodes). It now builds a node → DOM-group map in a single traversal and walks each element's ancestor chain, giving O(nodes + elements × depth). Elements that miss the map fall back individually to the original full-tree scan (slower but never wrong), which covers cases where the engine re-renders after the map was built
- Paragraph text is no longer concatenated twice: Markdown parsing previously joined the same paragraph content twice (once for display text, once for the parse-time snapshot); the same string is now reused
Engineering
- Three release-related configs are now type-checked:
eslint.config.mts/stylelint.config.mjs/manifest.jsontake part in the type check that precedesbuild(so a JSON syntax error inmanifest.jsonis caught at build time rather than at release scanning), which also retired ESLint's "default project" fallback configuration - Module resolution moved to
bundler: the old option is deprecated from TypeScript 6 and removed in 7, so editors report it continuously. Verified to pass with zero changes to the 400+ extension-less imports; the "ignore deprecation" silencer is deliberately not used — on the currently pinned TypeScript version it raises a configuration error and genuinely blocks the build - The docs manifest check now covers subdirectories: the
docs/list inAGENTS.mdpreviously validated top-level files only, so the detailed records underdocs/agents/were registered but never checked — a pointer to a non-existent file went unnoticed. They are now covered - Explicit declarations for dependencies and config: the dead-code check now declares project file boundaries (the conclusion matches the previous inference, with zero findings); the save-failure callback is declared as a function-typed property, so there is no implicit
thisat the type level and passing an unbound method reference would still be caught AGENTS.mdrefactored into an index plus detailed records: the main file went from 1628 to 183 lines, keeping hard rules (paired Don't/Do), a decision table, numbered workflows, a command table and an index; the 112 design conventions plus architecture/testing details moved into four documents underdocs/agents/. The existing constraint stands — the number of over-limit files without a registered exemption may only decrease- Line-count measurement unified: the previously reported "discrepancy" between registered and measured values was in fact two different counting conventions. Everything now uses the validation script's convention, and the docs note that PowerShell's line count should not be used
- Full suite: 60 files / 1820 cases (0.1.6: 58 / 1765)
- Docs: the Chinese and English READMEs gained a keyboard-shortcut table (search, undo/redo, edit, delete, fit to canvas, zoom to selection, including when each one yields to Obsidian); development now requires Node 20+
Compatibility notes
- Requires Obsidian 1.13.0+, desktop only; the
.mindmap.mdformat, command IDs and settings are unchanged — a fix/performance release with no migration needed - ⚠
styles.csschanged in this release (a self-contained icon-size fallback rule was added). Update bothmain.jsandstyles.css— replacing onlymain.jsis not enough - No new settings, no new third-party dependencies
0.1.6
0.1.6 是一次保真与性能版本:修复「拖入文件后节点不显示为链接」「文件重命名后节点仍指向旧目标」「附件嵌入编辑后出现重复文本」「md 尖括号路径往返漂移」四类缺陷;同时完成一轮量化驱动的性能收紧——图片拖宽不再逐帧整树重排(大图拖动卡顿消除)、行内解析入口短路(病态长行秒级阻塞消除)、「适应画布」在快速渲染模式下不再全树装配、段缓存内存封顶。无破坏性变更(.mindmap.md 格式、命令 ID 与设置项均未变)。
修复
- 拖入文件后节点不显示为链接:从文件浏览器拖入库内文档 / 附件创建的节点此前没有超链接样式、点击无效(须重开文件才恢复)——新建节点现即时补齐行级元数据(
mdRaw/mdDerivedText),拖入、粘贴、新建链接节点一次到位,保存逐字回写 - 文件重命名后节点仍指向旧目标:重命名被引用的文档或附件后,节点上屏的链接锚点保持旧目标直到重开文件——现在字段与渲染源一并更新(同一扫描器 + 同一改写规则),重命名即刻生效
- 附件嵌入节点编辑后保存出现重复文本:
![[附件.pdf]]节点被改写为「文本 + 附件嵌入」时,保存会写出多余的目标名(如测试附件.pdf ![[测试附件.pdf]]);已修 - 「移除引用」后的残留字段:覆盖写入分支的链接类字段清理统一收口——回写不再凭空多出旧引用
- md 链接 / 图片的尖括号目标(含空格路径):
[名](<路径 带空格>)、未编辑时此前被误判「需合成」而尾插漂移;现按 token 判定逐字回写(带自定义尺寸、尺寸未变的 md 图片同样不再漂移) - 图片引用的等价形态:子文件夹图片的裸名引用(加载期已转资源地址)解析等价视为未编辑、逐字回写;换图后新图不再被旧原文覆盖;图片重命名后按新路径合成
性能
- 图片拖宽不再卡顿(大图尤为明显):帧内直写渲染中
<image>宽度,不再逐帧走引擎命令与整树重排;收尾一次提交——一条撤销历史、一次保存调度,一次Ctrl+Z撤回整次拖动;拖回原尺寸不写入 - 病态长行解析提速:行内 token 解析入口短路(此前「大量
[起点但无闭合」的长行单行最坏 O(n²),可秒级阻塞打开 / 保存)——实测 21000 字符病态行 0.13ms、20000 字符 / 2000 token 密集行 0.37ms - 「适应画布」不再阻塞一段:快速渲染模式下改为按数据层几何计算(不再全树同步装配;5000 节点口径下该步约 1.1s)
- 保存视口幂等短路:重启视图时已在保存视口上则零调用,消除二次整树渲染
- 长行节点内存封顶:自绘段缓存加字符预算(条目上限对长行键失效,最坏约 20MB 常驻 → 1M 字符总预算)
- 其它:节点内容构建选项改为实例级复用(5000 节点此前约 2 万临时闭包);序列化的「跟随列表」判定按层惰性(同层 O(k²) → O(k));引擎重建少一次全树深拷贝
工程
- 测试全量 58 文件 / 1765 例(0.1.5 为 57 / 1739);
verify:visual的 history 探针改为 DOM 直写口径、perf-box 探针扩「适应画布」断言 - 移除 4 个过期设计文档(代码块方案与引擎补丁提案,结论已沉淀入库);
.gitignore排除 vitest 失败快照
兼容性说明
- 需要 Obsidian 1.13.0+,仅桌面端;
.mindmap.md格式、命令 ID 与设置项均未变更——纯修复 / 性能版本,无需迁移 - 本轮
styles.css无变化,更新main.js即可
Fixed
- Dropped files no longer render as plain text: nodes created by dragging a vault document/attachment onto the canvas had no hyperlink styling and were unclickable until the file was reopened — new nodes now get their line-level metadata (
mdRaw/mdDerivedText) immediately (drag-in, paste and new link nodes alike), so anchors render at once and saves write back verbatim - Renaming a referenced file now updates the node instantly: after renaming a referenced document or attachment, the on-screen anchor kept pointing at the old target until reopen — the field layer and the rendering source are now updated together (same scanner, same rewrite rules)
- No more duplicated text after editing an embedded attachment: rewriting a
![[file.pdf]]node into "text + embedded attachment" could save an extra copy of the target name (e.g.file.pdf ![[file.pdf]]) — fixed - Leftover fields after "Remove link": link-channel cleanup in overwrite branches is now a single shared sweep — saves no longer resurrect stale references
- Angle-bracketed targets in md links/images (paths with spaces): unedited
[name](<path with spaces>)/lines were misjudged as "needs compose" and drifted on save; now matched by token and written back verbatim (same for md images with unchanged custom sizes) - Equivalent forms of image references: bare-name references in subfolders (resolved to resource paths at load) count as unedited and round-trip verbatim; after replacing an image the new one is no longer overwritten by the old raw line; after renaming an image the new path is composed
Performance
- Image width dragging no longer stutters (large maps especially): each frame writes the rendered
<image>width directly instead of running an engine command and a full-tree re-layout; a single commit at the end yields one undo entry, one save schedule, and oneCtrl+Zreverts the whole drag; dragging back to the original size writes nothing - Pathological long lines: an entry short-circuit in inline token parsing (previously a worst case of O(n²) per line could block open/save for seconds) — measured 0.13 ms for a 21,000-char pathological line and 0.37 ms for a 20,000-char line with 2,000 tokens
- "Fit to canvas" no longer blocks: in fast-rendering mode it now computes from layout geometry instead of synchronously materialising the whole tree (~1.1 s at 5,000 nodes)
- Idempotent viewport restore: zero calls when the saved viewport is already applied, eliminating a second full-tree render
- Memory cap for long-line nodes: the self-drawn segment cache gained a character budget (its entry cap was ineffective for long keys — worst case ~20 MB resident — now capped at 1M chars in total)
- Also: node-content options are now instantiated once per view (previously ~20k temporary closures at 5,000 nodes); the serializer's "follow list" check is computed lazily per level (O(k²) → O(k)); one redundant full-tree clone removed from engine rebuilds
Engineering
- Full suite: 58 files / 1765 cases (0.1.5: 57/1739); the
verify:visualhistory probe now follows the DOM-write channel and the perf-box probe gained "fit to canvas" assertions - Removed 4 stale design documents (code-block plans and an engine patch proposal — conclusions already live in the code);
.gitignorenow excludes vitest failure snapshots
Compatibility notes
- Requires Obsidian 1.13.0+, desktop only; the
.mindmap.mdformat, command IDs and settings are unchanged — a fix/performance release with no migration needed styles.cssis unchanged in this release; updatingmain.jsis enough
0.1.5
0.1.5 聚焦内容渲染、编辑体验与结构保真:新增节点内代码块渲染与复制按钮、自绘节点内联编辑器、空节点保留;链接呈现统一为主题链接色;图片按自身原始大小展示(长边 480 封顶)并修复尺寸校正从未生效的缺陷;新建节点按层级回写为 Markdown 标题;数学通道三处实证修复。此外,本轮把导出 PNG 的保真度(与屏上渲染一致)和编辑/保存通道的可靠性(中心主题改名、混排双链自动拆分、外部修改保护、重复打开保护)一并收紧。无破坏性变更。
新增
- 节点内代码块渲染:围栏代码块渲染为块级盒子(等宽 + 代码背景、保留换行缩进、双轴滚动),语言信息行不进显示与复制;附 Obsidian 同款复制按钮(外观全内联、导出图隐身);按「轻量渲染」裁决不做语法高亮
- 自绘节点内联编辑器:双击 / F2 / 右键「编辑文本」直接在节点内编辑原文——
Esc保留、点击外部提交、Shift+Enter换行;弹窗保留为右键「在弹窗中编辑」备选入口 - 空节点保留:新建的空节点渲染为可见小方块,可点击编辑,不再退化为普通段落
改进
- 链接呈现口径统一:自绘节点内链接一律为主题链接色超链接文本(不再显示图标);URL 显示为地址文本;未解析链接弱化色
- 图片按自身原始大小展示:未设置尺寸的图按 1:1 自然尺寸渲染,长边超过 480px 等比缩小(只缩不放);显式参数(
![[图|300]])不受限;自动尺寸不回写文件 - 新建节点按层级回写标题:第 1
6 层分别回写为#######(与解析对称);第 7 层起写列表。保护:已有节点不改类型;列表/段落之下、同层有列表项、显式选列表标记的新节点 → 写列表
修复
- 图片尺寸校正从未生效:引擎对树数据做包装拷贝导致回灌匹配永远落空(所有图片停在默认尺寸);改为双通道匹配(身份 + uid)并新增首帧渲染完成补灌
- 数学通道:含
\sin等命名函数的公式不再退回字面(不可见操作符 U+2061 不再误判);$$ … $$开侧带空格正常进块级渲染、块内\\保真;孤立$与反引号配对边界与官方逐条一致(不修改) - 首次渲染时公式节点的尺寸:含行内/块级公式的节点首帧尺寸不再与最终尺寸不一致(按产物元素反查重排)
- 导出 PNG 与屏上渲染不一致(多轮修复,最终收敛):依次修复「多行节点第二行被裁」「LaTeX 节点文字不全」「临界节点换行后被裁」「数学节点末行文字压到节点底边框」——导出 SVG 注入宿主实际生效的字体/颜色变量(度量与屏上一致)、变量值校验改为结构性字符校验(放行中文环境字体栈)、导出侧 foreignObject 保留几何余量(宽 +12 / 高 +20)、导出侧数学容器按屏上实测高度钉扎
- 中心主题改名不重命名文件:默认渲染下编辑中心主题后文件未随之改名;现改为重命名
新名字.mindmap.md,并由 Obsidian 原生更新链接与反链 - 编辑混排节点不自动拆分双链:节点同时含「描述文字 + 文档/附件链接」时,编辑后未自动把链接拆为子节点(批量命令却正常);现已与批量行为一致(图片与外链仍留在原节点)
- 同一导图重复打开:同一
.mindmap.md在多个标签页打开会互相覆盖保存、丢失编辑;现在第二个标签会提示并让位,只保留一个编辑实例 - 外部修改保护:导图被外部改动(同步盘/其它窗口)时,自动保存会跳过并提示一次,避免覆盖他处改动;连续写盘失败达到阈值会挂起自动保存并提示(手动保存不受影响)
- 外链安全:节点内链接仅允许安全协议(
http/https/mailto/ftp(s)/obsidian/file),javascript:等危险写法不会被写成可点击链接
性能
- 打开 500 / 5000 / 10000 节点约 420
432 / 952979 / 1540~1760ms(较 0.1.4 继续下降);单次编辑 5.3 → 4.0ms;长会话内存无泄漏
工程
- 测试全量 57 文件 / 1739 例(0.1.4 为 49 / 1578);verify:visual 探针扩展至数学、代码块、图片回灌与层级回写场景
兼容性说明
- 需要 Obsidian 1.13.0+,仅桌面端;
.mindmap.md格式、命令 ID 与设置项未变 - 请同时更新
main.js与styles.css(复制按钮外观已内联,仅更新 main.js 亦可,双更新避免旧样式残留) - 图片 480px 长边上限为固定值;缩放只影响显示、不回写文件
- 同一
.mindmap.md只保留一个导图编辑实例:在第二个标签页再打开会提示并让位(避免保存互踩、丢失编辑)
New
- Code blocks inside nodes: fenced blocks render as a box (monospace + code background, line breaks/indentation preserved, two-axis scrolling); the language info line stays out of display and copy; with an Obsidian-style copy button (fully inlined appearance, hidden in exports); no syntax highlighting by design
- Inline editor for self-drawn nodes: double-click / F2 / context-menu "Edit text" edits the original line in place —
Esckeeps changes, clicking outside commits,Shift+Enterfor a newline; the dialog remains as the "Edit in dialog" fallback - Empty nodes preserved: newly created empty nodes render as a visible square (clickable, editable) instead of degrading into a paragraph
Improved
- Unified link presentation: links in self-drawn nodes render as themed hyperlink text (no icons); URLs show as address text; unresolved links are dimmed
- Images at natural size: images without a size render 1:1, proportionally capped at a 480px long edge (shrink-only); explicit parameters (
![[img|300]]) are honored; automatic sizing never writes back - New nodes write back as headings by level: levels 1
6 map to#######(symmetric with parsing); level 7+ falls back to lists. Guards: existing nodes keep their type; nodes created under a list/paragraph, with list-item siblings, or with an explicit list marker stay lists
Fixed
- Image size correction never worked: the engine wraps tree data so identity-based write-back never matched (all images stuck at the default size); now dual-channel matching (identity + uid) plus a first-frame flush path
- Math pipeline: formulas with
\sinetc. no longer fall back to literal text (invisible operator U+2061 no longer misjudged);$$ … $$with a leading space renders as a block and\\is preserved; the lone-$/backtick pairing boundary matches the official behavior case by case (left as-is) - First-frame size of math nodes: nodes containing inline/block formulas no longer render at a size that differs from the settled one (re-measure by product elements)
- Exported PNG no longer diverges from the on-screen render (multi-round fix): fixes "second line of a multi-line node clipped", "LaTeX node text incomplete", "borderline node clipped after re-wrap" and "last text line of a math node crossing the bottom border" — the export SVG now gets the host's effective font/color variables injected (same metrics as on screen), the variable-value guard rejects only structural characters (so CJK font stacks pass), foreignObjects keep a geometry margin (width +12 / height +20), and math containers are pinned to their measured on-screen height
- Renaming the central topic did not rename the file: with the default rendering, editing the central topic left the file untouched; it now renames to
NewName.mindmap.md, with Obsidian updating links and backlinks natively - Editing a mixed node did not auto-split links: a node mixing "description text + document/attachment links" kept its links after editing (while the batch command worked); editing now behaves like the batch command (images and external URLs stay put)
- Opening the same map twice: opening one
.mindmap.mdin several tabs could overwrite each other's saves and lose edits; the second tab now shows a notice and steps aside so a single editing instance remains - External-change protection: when the map is modified outside Obsidian (sync folder / another window) auto-save is skipped with a one-time notice instead of overwriting; after repeated write failures auto-save is suspended with a notice (manual save still works)
- Link safety: in-node links are limited to safe protocols (
http/https/mailto/ftp(s)/obsidian/file);javascript:and similar are never written as clickable links
Performance
- Opening 500 / 5000 / 10000 nodes at
420432 / 952979 / 15401760 ms (further down from 0.1.4); a single edit 5.3 → 4.0 ms; no memory leak growth in long sessions
Engineering
- The full suite is 57 files / 1739 cases (0.1.4: 49/1578); verify:visual probes extended to math, code blocks, image write-back and level write-back
Compatibility notes
- Requires Obsidian 1.13.0+, desktop only; the
.mindmap.mdformat, command IDs and settings are unchanged - Update both
main.jsandstyles.css(the copy-button appearance is inlined, so main.js alone works; updating both avoids stale styles) - The 480px image long-edge cap is fixed; scaling affects display only and is never written back
- Only one mind-map editing instance per
.mindmap.md: opening the same file in a second tab shows a notice and steps aside (prevents conflicting saves and lost edits)
0.1.4
0.1.4 是一次性能版本:对「打开大图」与「编辑流畅度」两条主路径做系统性提速——所有含文字的节点改由插件自绘渲染,跳过引擎成本为「0.13ms/字符」的逐字符文本测宽(含长文本的大图打开从数十秒降到秒级);打开过程改为分片渲染,界面全程不冻结;另以五处引擎自有补丁收敛编辑与渲染帧成本(一次编辑 47.6 → 5.3ms、空渲染落地 28.1 → 3.0ms)。同时向内收敛两项工程基建:引擎补丁体系(vendored 源入仓 + 可复现重打包)与社区目录校验对照。无破坏性变更(.mindmap.md 格式、命令 ID 均未变),新增一个默认开启的设置项。
性能
- 所有含文字的节点改用自绘渲染(新设置「文本节点快速渲染」,默认开启):打开大图的成本主体是引擎的逐字符文本测宽(每字符一次离屏测量,0.13ms/字符),字符越多越慢——含长文本段落的大图尤其显著。现改为全部含文字节点由插件自绘(跳过测宽,尺寸走内容寻址缓存),实测 5000 节点:含长文本节点约 59 秒 → 约 1.4 秒,纯短文本 8.6 秒 → 约 1.4 秒。边界:空文本 / 含图 / 整行注释等节点不接管;双击节点改为打开编辑弹窗(引擎内联编辑框对自绘节点不可用);该设置仅对之后打开的文件生效(切换时不重建当前大图,避免卡顿);关闭即恢复引擎原生文本渲染与原位编辑
- 打开大图时界面不再冻结:整树渲染改为分片执行(每个节点一个宏任务,浏览器可在其间响应输入与绘制),主线程单块阻塞从约 1.1 秒降到每块约 0.2 毫秒——打开全程可交互;总时长代价经同批对照仅约 +2.4%(5000 节点 1137 → 1164ms)
- 打开成本的三处继续收敛(5000 节点实测):① 拖宽手柄的全局监听器由「每个节点 3 个」改为拖拽会话内惰性注册(15000 → 0,打开 -13%;同时修复「节点销毁后监听器永久持有」的内存泄漏);② 布局的兄弟子树平移消除二次复杂度(实测 293 万次节点位移 → O(n),打开 -6%);③ 自绘尺寸按内容寻址缓存 + 首帧前预测量(一遍内容构建、重复打开与首次打开都免重复测宽)
- 编辑与渲染帧成本(500 节点实测):一次编辑 47.6 → 5.3ms(-89%)、空渲染落地 28.1 → 3.0ms(-89%)——三处引擎修复:兄弟节点查找 O(n) → O(1)(原先每节点每轮把全部兄弟拷进新数组线性查找)、数据快照比较去掉 JSON 序列化、SVG 属性同值写短路(不再触发无谓的样式重算与重绘)
工程
- 引擎自有补丁体系:fix.3 引擎源码入仓(
vendor/upstream/)+ 9 处自有补丁(vendor/patches/与 upstream 内),经npm run build:vendor可复现重打包,产物 sha256 由契约测试钉住——升级与审计链路完全可查(清单见vendor/BUILD.md) - 测试:全量 49 文件 / 1578 例;
npm run verify:visual现有 26+ 断言探针,并新增三组 opt-in 测量通道(打开基准--bench-open、编辑帧分解--perf --perf-ops、布局计数--bench-open+BENCH_VARIANT=count-layout)——本版全部性能数据均由这些通道的真实墙钟差分测得 - 社区目录校验对照:新增
npm run lint:scanner(用社区目录 scanner 的固定版本依赖集与官方规则面本地预检),接入 CI 与发布门禁;scanner 工作流、官方模板、开发者指南三条对照线均 0 缺口
设置与交互变化
- 新增设置「文本节点快速渲染」(默认开启,可关闭)——见上方性能段;变更仅对之后打开的文件生效
- 自绘节点的文本编辑统一走编辑弹窗(双击 / 右键「编辑文本」/ F2),宽度拖拽手柄只在自绘节点上显示
兼容性说明
- 与 0.1.3 一致:需要 Obsidian 1.13.0+,仅桌面端(
isDesktopOnly: true) .mindmap.md格式、既有命令 ID、快捷键与其余设置项均未变更;本版为纯性能 / 稳定性改动,无需任何迁移
Performance
- All text-bearing nodes are now rendered by the plugin (new setting "Fast text node rendering", on by default): the dominant cost of opening a large map was the engine's per-character text measurement (one off-screen measurement per character, 0.13 ms/char) — the more text, the slower the open, with long paragraphs being the worst case. Every text-bearing node is now drawn by the plugin (measurement skipped; sizes come from a content-addressed cache). Measured with 5,000 nodes: a map with long-text nodes went from ~59 s to ~1.4 s; short-text-only from 8.6 s to ~1.4 s. Boundaries: empty-text / image-bearing / fully-commented nodes are not taken over; double-clicking a node now opens the edit dialog (the engine's inline editor does not work on self-drawn nodes); the setting applies to maps opened afterwards (switching it does not rebuild the current large map, avoiding a stutter); turning it off restores the engine's native text rendering and inline editing
- The UI no longer freezes while a large map opens: full-tree rendering is now sliced (one macrotask per node, so the browser can respond to input and paint in between) — the worst single main-thread block drops from ~1.1 s to ~0.2 ms per slice, so the app stays responsive throughout the open; the total-time cost measured in a same-batch comparison is only about +2.4% (5,000 nodes: 1137 → 1164 ms)
- Three further reductions in open cost (measured with 5,000 nodes): ① the width-handle global listeners went from "3 per node" to lazy registration inside a drag session (15,000 → 0; -13% open time; also fixes a memory leak where destroyed nodes were kept alive by window listeners); ② the layout's sibling-subtree shifting lost its quadratic complexity (2.93M node moves → O(n); -6% open time); ③ self-drawn sizes are cached by content and pre-measured before the first frame (one content build only; no repeated measurement on first or repeat opens)
- Edit and render frame cost (measured with 500 nodes): one edit 47.6 → 5.3 ms (-89%), an empty render pass 28.1 → 3.0 ms (-89%) — three engine fixes: sibling lookup O(n) → O(1) (previously copying all siblings into a new array for a linear scan on every node every pass), data-snapshot comparison without JSON serialization, and same-value SVG attribute writes are short-circuited (no more pointless style recalculation and repaint)
Engineering
- A first-party engine patch system: the fix.3 engine source is vendored (
vendor/upstream/) with 9 in-house patches (vendor/patches/plus in-tree ones), rebuilt reproducibly vianpm run build:vendor, with the artifact sha256 pinned by a contract test — upgrade and audit trails are fully inspectable (seevendor/BUILD.md) - Tests: the full suite is 49 files / 1578 cases;
npm run verify:visualnow has 26+ assertion probes, plus three opt-in measurement channels (open benchmark--bench-open, edit-frame breakdown--perf --perf-ops, layout counters viaBENCH_VARIANT=count-layout) — every performance figure in this release was measured through these real-wall-clock channels - Community-catalog parity checks: new
npm run lint:scannerruns the community catalog scanner's pinned dependency set and rule set locally, wired into CI and the release gate; the scanner-workflow, sample-plugin and developer-guideline audits all found zero gaps
Settings and interaction changes
- New setting "Fast text node rendering" (on by default, can be turned off) — see Performance above; the change applies to maps opened afterwards
- Text editing on self-drawn nodes goes through the edit dialog (double-click / context menu "Edit text" / F2); the width-drag handles appear only on self-drawn nodes
Compatibility notes
- Unchanged from 0.1.3: requires Obsidian 1.13.0+, desktop only (
isDesktopOnly: true) - No changes to the
.mindmap.mdformat, existing command IDs, hotkeys or other settings; this release is performance / stability-only and needs no migration
0.1.3
0.1.3 是一次性能与验证基建版本:修复「多节点导图打开时卡顿严重、加载很久」(打开时的默认视口居中不再把整棵树临时装配进 DOM),并收敛三处重复付出的高频成本(打开含图笔记不再为解析一个图片地址构建全库索引、文件重命名/删除的引用预检与拖拽落点识别的每帧/整树开销下降)。同时把 verify:visual 的四个固定延时型探针改为「渲染事件 + 轮询」的就绪判定,消除负载机器上的探针假失败。无破坏性变更(.mindmap.md 格式、命令 ID、设置项均未变;最低支持版本仍为 Obsidian 1.13.0)。
性能
- 打开大图不再为「整体居中」把整棵树临时装配进 DOM(用户实测反馈「多节点打开卡顿严重、加载很久」):性能模式下视口外节点会被引擎回收出 DOM,而打开时的「100% + 整体内容居中」需要全图包围盒——旧实现先把整棵树同步装配进 DOM 再测量,且该动作在打开窗口内被重复触发多轮(首帧渲染结束、其自身事件重入、150ms 兜底、图片尺寸回灌后的补居中),节点越多每轮越重。现改为直接遍历渲染树的布局几何求包围盒(与引擎裁剪判定同源,零 DOM 装配、纯计算),并在「比例已是 100% 且内容已居中」时整体跳过(不再触发多余重绘)——「适应画布」仍是引擎的全量通道,行为不变
- 解析一个图片地址不再构建全库索引:打开含图笔记时,首个
app://…地址的解析此前会在惰性索引未建时同步构建全库索引(10 万文件库实测 300ms+ 的同步阻塞);现优先「从地址提取路径 → 直查文件 → 校验地址全等」(实测 ~2µs),未命中才回退索引——历史形态的兜底行为不变 - 库事件与高频查询的成本收敛:
create事件由「整体失效 + 下次全量重建」改为增量补建(批量导入 / 外部同步风暴期不再反复付全库成本);索引未命中自愈的数量比对加时间窗节流;同名冲突查询走共享索引计数(新建文档链接不再逐次全库扫描);拖拽辅助线每帧不再为全树节点分配临时坐标对象(先做平方距离预筛);文件重命名 / 删除的引用预检不再为每个节点拼接比对串(2000 节点实测 0.68ms → 0.51ms,「空 needle」也不再恒命中);纯文本行的解析少一次数组分配
工程
- 测试:全量 48 文件 / 1567 例(三轮新增 / 改写覆盖索引增量与校验节流、资源地址直解与异常兜底、引用预检零拷贝、拖拽几何一致性、数据层包围盒与幂等短路等契约);
npm run verify:visual现有 16 个断言探针(新增 perf-box:大图居中不装配全量 DOM + 数据层盒与 DOM 盒口径一致) - 验证基建加固:
verify:visual的四个固定延时型探针(perf / image / count / layout)改为「node_tree_render_end事件 + 轮询」的就绪判定(读 DOM 结构 / 计数的探针用严格模式),并修复构建器计数器被邻图渲染污染的问题——负载机器上的稳定假失败(探针读到renderer.root为 null 的中间态)就此消除
兼容性说明
- 与 0.1.2 一致:需要 Obsidian 1.13.0+,仅桌面端(
isDesktopOnly: true) .mindmap.md格式、既有命令 ID、设置项与快捷键均未变更;本版为纯性能 / 稳定性改动,无需任何迁移
Performance
- Opening a large map no longer attaches the whole tree to the DOM just to centre it (user feedback: "opening a multi-node map stutters badly and takes a long time"): in performance mode off-viewport nodes are reclaimed out of the DOM, while the opening "100% + centre all content" needs the full-map bounding box — the old implementation first attached the entire tree to the DOM synchronously to measure it, and that step was repeated several times during the opening window (first frame, its own event re-entry, the 150 ms fallback, and the re-centring after image-size corrections); the more nodes, the heavier each round. The box is now computed directly from the render tree's layout geometry (the same source the engine's culling check uses — zero DOM attachment, pure computation), and the whole step is skipped when the view is already at "100% and centred" (no redundant redraw triggers). "Fit canvas" still uses the engine's full-load path, unchanged
- Resolving one image address no longer builds the whole-vault index: opening a note with images used to synchronously build the lazy whole-vault index for the first
app://…address (measured 300 ms+ on a 100k-file vault); it now tries "extract the path → look the file up → verify the address matches" first (~2 µs) and falls back to the index only on a miss — historical-format fallback behaviour is unchanged - Lower cost for vault events and hot lookups:
createevents now incrementally add their entries instead of invalidating the whole index (bulk imports / external sync no longer pay the full-vault cost over and over); the self-healing count comparison on an index miss is throttled by a time window; the same-name conflict check reads shared index counts (creating a document link no longer scans the whole vault per call); drag guides no longer allocate a temporary coordinate object per node per frame (squared-distance pre-filter first); rename / delete reference pre-checks no longer concatenate a comparison string per node (2000 nodes: 0.68 ms → 0.51 ms, and an empty needle no longer matches everything); plain-text Markdown lines skip one array allocation
Engineering
- Tests: the full suite is 48 files / 1567 cases (new / rewritten across the rounds: index increment and validation throttling, resource-address direct resolution and exception paths, zero-copy reference pre-checks, drag geometry parity, the data-layer bounding box and idempotent centring);
verify:visualnow has 16 assertion probes (the new perf-box probe: centring a large map attaches no whole-tree DOM, and the data-layer box matches the DOM box) - Verification hardening: the four fixed-delay probes (perf / image / count / layout) now use an "engine render event + polling" readiness check (strict mode for probes that read DOM structure or counts), and the builder counter is no longer installed on the shared options object (neighbouring maps' first frames used to leak calls into its idle-render window) — the stable false failures on loaded machines (probes reading
renderer.rootwhile it was still null) are gone
Compatibility notes
- Unchanged from 0.1.2: requires Obsidian 1.13.0+, desktop only (
isDesktopOnly: true) - No changes to the
.mindmap.mdformat, command IDs, settings or hotkeys; this release is performance / stability-only and needs no migration
0.1.2
0.1.2 是一次「节点内富文本 + 结构整理 + 图片增强 + 回写保真 + 逐条对齐 Obsidian 官方交互口径」的版本:新增节点内富文本(方案 B)——节点里的链接渲染为可点文本(多链接各自可点、URL 恢复为可点地址、悬停预览跟指针)、轻标记(粗体 / 斜体 / 行内代码 / 删除线)、超长节点截断展示与多 token 编辑后原位保真;新增混排双链拆分(选中节点命令、编辑后自动、全文批量三种入口),段落(文本)节点支持首行图片渲染;图片链路做了成体系的修复与性能优化(加载期尺寸不回写、调宽写入合并、缓存 LRU / 跨会话);回写层集中修复了一批编辑后数据降级问题(一行多图的嵌入语法、一行多链接 / 多 URL 的非首个 token、链接引用定义行),并再次调整「纯双链化」写入语义。同时修复了打开导图时的视口偏差与**「先亮根节点居中、再跳成整体居中」的可见跳变**(首帧几何三坑,见「修复」),设置面板调整改为原地生效、不再重建引擎(性能阈值 / 主题不再重置视图,见「性能」)。底层 Markdown 无损往返、三类链接图标、官方尺寸语法、PNG 导出、搜索、性能模式均保持兼容。本版另含一轮对照官方指南与 API 的合规审计(修复发布阻断、许可声明分发、视图与引擎的生命周期泄漏,收回 ESLint 配置层的三处静默放宽,并补上 CSS 检查与发布元数据门禁——见「工程」)。
新增
- 混排双链拆分:与描述文字混排的文档 / 附件双链(如
关于 [[冬天]] 和 [[秋天]] 的相关问题)可抽离为子节点——父节点保留描述文字(链接处替换为可见名),每条链接一个子节点(各自拥有图标 / 悬停 / 点击)。三种入口:① 命令「拆分节点内双链为子节点」;② 编辑此类节点后自动拆分(设置项「自动拆分混排双链」,默认开启,可关闭;检查对象为编辑期捕获的节点,编辑提交后即使快速切换选中节点也不会漏拆);③ 命令「拆分文档内全部混排双链」批量处理整篇(含未编辑的存量节点)。图片与外链不拆分、链接引用定义行不拆分、同一目标去重、重复执行幂等 - 段落节点首行图片:段落(文本)节点首行的
![[图.png]]现在会渲染为节点图片(可更换 / 移除);段落中的链接语法仍按原文逐字保真 - 节点内富文本(方案 B):节点里的链接渲染为可点文本(点击跳转、
Ctrl/Command+点击在新标签打开)——一行多枚链接各自可点(此前只有第一枚生效)、悬停预览两级制(悬停某枚链接文字 → 预览那一枚;悬停节点其它位置 → 预览该节点承载的链接);URL 与其它内容混排时恢复为可点地址(此前一律仅图标);轻标记**粗体**/*斜体*/`行内代码`/~~删除线~~在节点内直接呈现(无链接的节点同样生效),文件原文逐字保真;段落(多行)节点里的链接同样可点。含链接 / 轻标记 / 超长文本的节点改由插件文本弹窗编辑(双击 / 右键「编辑文本」/ F2 三入口共用,弹窗里是全文),自绘内容样式随导出 SVG 内联带出(PNG 导出保真) - 超长节点截断展示:超过 2000 字的节点只渲染开头 +
…(悬停有说明),完整内容仍在文件中、双击可查看 / 编辑全文 - 节点编辑弹窗:原文模式(编辑入口按节点类型分流):纯链接节点仍是「改别名」(原语义不变);其余富节点(混排链接 / URL / 轻标记 / 超长)改为编辑文件里的那一行——
[[双链]]、URL、**粗体**语法全部可见可改,弹窗内实时显示「节点将显示为」预览,保存后逐字写回该行(重解析 → 行内字段整体重建 ⇒ 未编辑态)。此前这类节点编辑的是显示文本:双链只剩剥壳名、外链(icon-only)连影子都没有(用户实测反馈) - 多 token 编辑后「原位」保真:一行内多枚链接 / URL / 图在编辑节点后按原文位置写回(此前一律挪到行尾);无法定位时才回落到行尾追加
- 交互对齐 Obsidian 官方口径(逐条对照官方帮助库):① 打开链接的落点补齐两档——
Ctrl/Command+Alt+点击=新标签组,再加Shift=新窗口(此前只有「新标签」);② 未解析链接弱化——指向尚不存在目标的链接显示为弱化配色(同阅读视图),且悬停不再弹预览;③ 行内语法扩至官方清单——新增==高亮==、__粗体__/_斜体_、***粗斜***、双反引号代码跨度,并正确处理反斜杠转义(\*a\*显示*a*,此前会被误渲染成斜体)与%%注释%%(节点内隐藏,文件里保留),与阅读视图一致;④ 拖入系统文件支持任意类型(此前有类型白名单),可嵌入的附件默认写嵌入语法![[报告.pdf]],按住Ctrl(Win/Linux)/Option(mac)拖入则不复制、改插入指向原位置的绝对链接[文件名](<file:///…>)(点击交系统默认应用)——与 Obsidian 的拖放一致
修复
- 回写保真:一行多张库内图片:
![[a.png]] 与 ![[b.png]]此前任何保存都会把第二张图的嵌入语法降级为纯文件名文本(被误判为「残留链接」);现按目标类型区分,图片类嵌入完整保留 - 回写保真:一行多链接 / 多 URL / 多图:编辑此类节点后,非首个 token 此前会降级为文本(多裸 URL 连内容都会整体丢失);现按原文完整保留——内容与语法不丢,为保持行结构稳定,额外 token 统一附于行尾,所在位置可能与原文不同(本版后续已改为原位写回,见「新增」最后一条)
- 回写保真:链接引用定义行(参考式
[ref]: …/ 脚注[^1]: …):此前编辑含裸 URL 的定义行会把 URL 挪到行尾、[ref]引用全部失效;现定义行整体按纯文本保真,拆分命令同样跳过定义行 - frontmatter 写盘取磁盘现值(数据安全):写盘前重读文件,文件头以磁盘当前内容为准——视图打开期间在属性面板 / 其它窗格 / 其它设备(同步)改动的属性,不再被加载时的旧快照覆盖(此前偶有「笔记属性丢失」)
- frontmatter 解析边界对齐 Obsidian:空属性块(
---与---相邻、属性被删光)不再被并进正文;YAML 块标量(|/>)内部的---不再截断属性;开头 BOM 计入文件头原样保留(此前会导致首个标题降级为段落、首个列表项多出假缩进) - 图片:加载期自动尺寸不再回写:按原始比例自动算出的显示尺寸(非用户意图)不再写出
|宽度——此前从未编辑过的行可能在保存后凭空多出尺寸参数 - 图片:右键「移除图片」后不再复活:此前在列表 / 标题节点移除图片,下一次保存会把原图写回(图片回写元数据被清理后,「图片已移除」判定失效、整行被逐字写回);现判定不再依赖字段残留——
image为空且行内仍有图片语法(行内![[…]]/)即按移除处理,与段落节点同口径 - 拆分:运行期资源地址不再污染笔记:此前图片节点的混排拆分会把运行期资源地址(
app://…#图.png?时间戳)当作附件抽成子节点、URL 编码片段被写进父文本;现与序列化同入口携带app,行内出现app://一律放弃拆分 - 删除笔记导致链接被静默降级为纯文本(数据安全,用户实测反馈):此前删除笔记(或移入
.trash)时,插件会把导图节点上的链接引用清空——但节点文本与行内台账不动,于是下一次保存把[[笔记A]]写成了纯文本笔记A(同一行里其它链接因台账仍在而保留语法,表现为「首个链接降级」),不可逆。现改为:链接只改不删——删除/回收站场景保留为「未解析链接」(与 Obsidian 一致,同名笔记重建即恢复),重命名仍照旧改写;附件整条删除——引用与节点内的可见文字一起去掉(只清引用会留下「报告.pdf」这样的纯文本残留),因此变成空的叶子节点连节点一并摘除,带子节点的节点则退化为保留引用(不连带删子树);内嵌图片仍按删除即清除(目标缺失会在节点上留下坏死图块) - 超长节点导致白屏 / 卡死后关闭:根因在引擎默认 SVG 文本路径——其换行逐字符迭代、每加一字重新拼接整行并重新测量一次,单行开销随字数二次增长(无头 Chrome 同机实测:20k 字单行 ≈ +2.8 秒 / 节点,整轮 1.1s → 3.9s),而原「超长文本回落引擎」策略恰好把最长的行送进该通道。现改为长文本一律自绘接管 + 截断展示:同负载整轮回到 1.1 秒(与无长文本基线持平)
- 轻标记在无链接节点不生效:
**粗体**此前只有在含链接的节点里才渲染,否则原样显示标记符;现「含链接或轻标记」都接管 - 导出 PNG 掉样式:自绘内容的布局 / 排版 / 配色 / 轻标记样式改为全部内联在元素上(引擎导出只注入自身 CSS,不加载插件 styles.css;此前依赖类规则会让导出图尺寸与排版错乱)
- 富节点双击无响应:引擎的节点编辑框对自绘节点静默失效;现双击 / 右键「编辑文本」/ F2 统一走插件文本弹窗(弹窗内为全文)
- 打开导图首次渲染就偏移(用户实测反馈):打开
.mindmap.md/ 文档模式导图时内容整体偏向一侧、点「适应画布」能立刻拉回(故一直像「偶发」)。根因是首帧居中用陈旧几何——引擎只在创建与resize()时缓存容器矩形(elRect/width/height),而首帧后工作区布局 settle、文档模式工具栏重建都会改容器尺寸与位置且不触发view.onResize,缩放锚点与内容包围盒换算便同时按旧画布算。现取视口前先按实时容器同步引擎几何再换算(getLiveCanvasRect/syncCanvasGeometry),内容包围盒也改以实时容器为原点 - 保存视口可能把内容推出画布:保存的视口按「当时的容器与内容布局」记录,容器尺寸大改或内容变化后恢复它可能让画面停在画布外(打开即空白)。现恢复后做落界校验:明确判定不可见则放弃恢复、回退默认居中;无法判定(性能模式、引擎中间态)时保持原状,不误伤用户自己的视口
- 打开时先亮「根节点居中」再跳成「整体居中」(可见跳变):引擎默认
initRootNodePosition = [center, center],首次布局把根节点摆在画布中心,而插件的视口设置发生在 150ms 兜底定时器里 ⇒ 中间态被绘制、随后跳变。现在在首次布局落地的事件回调里同帧应用视口(node_tree_render_end,属布局任务内提交,浏览器不会绘制中间态),并为「布局后内容还会变」的窗口留 2 次重算预算(resize触发的再布局、图片尺寸回灌都会改包围盒),用尽即停手,不抢用户之后的视口 - 含图笔记的居中在图片尺寸落定后才准:图片尺寸探测改为不挡首帧后,真实尺寸回灌会改节点尺寸与包围盒;现在回灌后(仅当当前视口仍是打开时自动设置的那个)补一次居中——用户已平移 / 缩放则完全不打扰
性能
- 图片调宽写入合并:拖拽按最小步长(8px)合并写入、松手补写最终尺寸——整树重排开销降一个量级;陈旧帧按会话所属引擎校验,不写进新引擎
- 图片尺寸缓存真 LRU:命中提升,热点图不再被冷图挤出后重复解码
- 跨会话尺寸缓存:经官方
App#saveLocalStorage按库隔离——第二次打开同一库的图片尺寸零解码探测(外链与探测失败不落盘、数据损坏静默降级) - 图片弹窗:预览按解析结果去重,输入中间态不再逐键重建 DOM
- 节点渲染成本封顶:自绘路径的每节点上界 = 2000 字(与文本总长无关,超长节点不再随字数变慢);行内段序列按原文内容寻址缓存,拖拽 / 缩放期间同一行不再重复解析
- 设置面板调整「即时生效、不重建引擎」:此前应用设置对每个打开的视图一律重建(整树深拷贝 + 销毁重建引擎 + 全量重渲染 + 工具栏重建),而性能阈值是 step 100 的滑块、拖一次即几十轮。现按按键差集分流——主题(
setThemeConfig)与性能模式 / 阈值(updateConfig,引擎显式支持运行时开关)原地生效;默认布局 / 默认连线样式对已打开的图本就不生效,直接跳过;只有拖拽开关与语言仍需重建(引擎侧只有创建期通道:拖拽由创建期注册的 Drag 插件承担,语言在创建期被闭包捕获) - 渲染经济:一次动作至多一次布局落地:审计插件侧全部
render()调用点,查出并修掉一处冗余(关闭性能模式时与引擎自带的一次重复 ⇒ 白白多一轮全树渲染);verify:visual新增 render-eco 探针,把 8 条代表性动作的布局落地次数钉成回归闸门 - 大图撤销深度的内存预算:命令历史逐条保存「整树 JSON 快照」,单条 ≈ 节点数 × 0.4KB,原先的固定条数上限让单视图历史常驻内存随图规模失控(旧策略 500 节点图 ≈ 91MB/视图,反重于 2000 节点图 ≈ 70MB)。现按 30MB/视图的内存预算反推条数上限:500 节点 → 163 条、2000 节点 → 40 条、超大图保底 30 条(预算宽裕的小图仍是引擎默认 500 条)——单视图历史内存钉在预算内,撤销深度仍远超日常所需
行为变化提示
- 链接写入语义再修订(0.1.1「无条件覆盖」→ 本版仅「完全空白」节点覆盖):把链接(文档 / 附件 / URL)挂到节点时——已有任何内容的节点(文字 / 原链接 / 图片 / 行内额外 token)一律建为子节点,节点原内容完全不动(行内多链接不再被覆盖牵动);只有完全空白节点才覆盖为链接节点。URL 单独成节点时仍是「仅图标」(节点文本为空;与其它内容混排时显示为可点地址)。注意:「一键改链」入口随之取消——改链请先「清除链接」再添加,或直接编辑节点文本
- 节点内链接现在是可点文本(此前只有第一枚可点、URL 仅图标);含链接 / 轻标记 / 超长文本的节点不再有引擎原位编辑框,双击弹出插件弹窗
- 编辑弹窗分两种模式(同上「新增」条目):纯链接节点 = 改别名(不变);其余富节点 = 编辑原文行(此前编辑的是显示文本,链接语法与外链都看不见)。原文模式下取消或未改动不写盘
- 节点宽度(拖左右边框):只有自绘节点能拖——含链接 / 轻标记 / 超长文本(>2000 字)的节点自绘渲染,拖宽生效且高度随换行跟随;纯文本与含图节点不显示该手柄(引擎的 SVG 文本路径不支持按节点改换行宽度,此前手柄可拖但毫无反应)。拖宽仅当前会话有效(正文保持纯 Markdown,宽度不落文件),重开回落 500px 折行上限
- 超过 2000 字的节点只显示开头(
…标示,悬停有说明);文件内容与data.text不受影响,双击可查看 / 编辑全文 - 含图节点不受这些渲染变化影响:图片仍由引擎图片通道渲染(图片节点的混排文本不再叠加文档页图标);含图 + 超长文本的节点仍由引擎换行承担(无法接管),建议把该节点拆短
F2对齐官方:未选中节点时 F2 交回 Obsidian=重命名当前文件(官方语义);选中节点时仍是编辑该节点(中心节点被选中时同理——中心节点文本即文件名)。此前无条件吞键,等于在导图视图里用不了官方 F2- 新增设置「自动更新链接」(对应官方「自动更新内部链接」,默认开启):关闭后重命名不再改写链接(变未解析)、删除不再清理引用与附件,与官方关掉该设置的表现一致
- 链接语法跟随官方 Wikilink 设置:官方「使用 [[Wikilinks]]」开启(官方默认)行为完全不变(恒写
[[双链]]、保留自绘文档页图标);用户关闭该设置时,新文档链接写 md 形态[显示名](路径.md)(走 hyperlink 通道),重命名改写同样支持该形态(此前只认[[…]],md 形态链接改名后会失效) - 拖入类型白名单取消:库内文件、系统文件、链接弹窗三处的「附件」判定统一为解析侧口径(非文档扩展名即附件,无扩展名视为文档)——手写
[[说明.txt]]能显示、拖入.txt也同样能挂上(此前拖入会被拒、手写却可以,属两侧口径不一致)。随之删除三份失效白名单与两条失效文案 - 不可渲染的库内文件点击改为交系统默认应用(此前只有音视频外跳,其余弹「无法预览」)——白名单取消后任意扩展名都可能入库,弹提示等于「链了打不开」
- 大图上的撤销步数减少:历史上限改为按内存预算反推(见「性能」)——可回退步数随图规模收敛(500 节点 163 步 / 2000 节点 40 步 / 保底 30 步),换来单视图历史内存不超过 30MB
工程
- 测试(全量 42 个文件 / 1327 例):新增
tests/view-split-links.test.ts(自动拆分:设置开关 / 未编辑 / 编辑中与候选集语义)、tests/agents-md-sync.test.ts(文档-代码同步契约:src/**与tests/**每个文件必须登记在 AGENTS.md,漏登记即红灯);tests/links-split.test.ts扩为全规则矩阵(含链接引用定义行跳过、拆分后多 token 重建);tests/md-roundtrip.test.ts补回写保真用例组(一行多图、多链接 / 多 URL / 多图编辑后保真、定义行 4 形态 + 不误伤对照);frontmatter(CRLF / 空块 / 块标量 / BOM)、图片尺寸与缓存、自动拆分候选、引擎事件转发契约等回归补强 - 文档:AGENTS.md 重构为 K 编号分组体系(K1–K49 稳定引用 ID + 「新增功能检查清单」+ 「本文件维护规则」);标准文档同步(拆分规则、回写保真与已知降级表);README 修正链接写入语义
- 校验:类型检查 0 错误、eslint 0 警告、全量测试全绿
- 测试(本轮新增):全量 44 文件 / 1401 例;新增
tests/node-inline-content.test.ts(段序列、接管判定、轻标记、超长截断、内联样式契约)、tests/md-line-write.test.ts(原文写入:行内字段整体重建、逐字往返锁、多行、未闭合语法)、tests/view-wikilink.test.ts锚点级悬停 6 例、tests/view-hotkeys.test.ts编辑入口两种模式分流;tests/md-inline.test.ts补台账登记边界;npm run verify:visual扩为 9 个场景 + 6 个探针(自绘装配、测宽同源、合成点击命中锚点、导出内联样式、30 富节点规模、超长截断、段落可点) - 单一台账:额外 token 的
mdExtraTokens并入mdSegments(first区分首 token / 额外 token),解析、拆分重建、清除链接三处各只有一处实现 - 修复(撤销失效):拆分文档内全部混排双链(以及重命名/删除文件的引用更新)此前用引擎
setData落数据——它内部clearHistory()会把历史清成「只剩新状态一条」,之后Ctrl/Command+Z永远无事发生(用户实测)。现统一走engine/mindmap.replaceMindMapData(updateData+CLEAR_ACTIVE_NODE):只追加一条历史,一次撤销即回到操作前;契约由 vendor-contract(引擎必须保留updateData/CLEAR_ACTIVE_NODE)与拆分、引用更新三处回归锁定 - 测试(对齐轮):全量 44 文件 / 1437 例;
tests/node-inline-content.test.ts补轻标记扩展 / 转义字符集 / 注释 / 未解析标记,tests/view-wikilink.test.ts补修饰键矩阵、中键(auxclick)与悬停预览两级制用例(锚点优先含「首链为外链时的行内库内 md 链接」实测场景、节点级兜底、锚点为外链时回落);2026-09-16 定稿:曾一度只留锚点级(常规悬停静默无反应)或只留节点级(首链为外链时行内库内链接无法预览),且「未解析不预览」的解析预检与核心职责重复——现为锚点优先 + 节点级兜底、均不做解析预检;tests/view-dnd.test.ts重写为「任意类型可导入 /Ctrl绝对链接 / 无扩展名走文档通道」,tests/url.test.ts补file:///绝对链接三形态往返,tests/view-node-actions.test.ts补applyExternalLink与mdEmbed;npm run verify:visual扩为 10 个场景 + 6 个探针(新增syntax场景:<mark>/__粗__/ 转义 / 注释 /is-unresolved,并含两条负向自检:解析桩恒真、注释不隐藏,均实测报红) - 修复(拖边框改宽后节点高度不跟随):自绘(富)节点「选中后拖动左右边框改宽」时宽度变、高度不变。根因在引擎分工:引擎只对自绘节点给出
ew-resize边框手柄,拖拽中每帧只把宽度写进节点字段node.customTextWidth(reRender([])不重建自绘内容),松手也不重建(整树render()/needLayout均无效,无头实测元素 inline width 为空),而自绘节点的宽高全部来自内容元素的离屏测宽。现两处配合:自绘内容读 `customText...
0.1.1
0.1.1 是一次「体验增强 + 链路修正」的版本:新增连线样式切换(工具栏 + 设置,按文件持久化)、右键菜单重置缩放、维基链接悬停预览扩展到三类节点,并修正了悬停预览的链路判定与「干净双链化」语义。底层 Markdown 无损往返、三类链接图标、图片与嵌入语法保真、PNG 导出、搜索、性能模式均保持兼容,且行为口径更清晰。
新增
- 连线样式切换(工具栏 + 设置):工具栏新增「连线」下拉,可在
自动/曲线/直连/折线间切换,偏好按文件持久化到视图状态存储(不写入正文)。设置面板新增「默认连线样式」。目录组织图/时间轴/鱼骨图为布局类原生直线、不可切换,控件只显示「自动」 - 右键菜单「重置缩放」:右键菜单新增「重置缩放」项(rotate-ccw 图标),将视口恢复为 100%
- 维基链接悬停预览扩展到三类节点:文档双链 / 文档嵌入(
mdWikiLinkpath)、双链附件 / 嵌入附件 / 拖入的库内附件(attachmentUrl回形针通道)现均可悬停触发 Obsidian 原生页面预览 / 附件预览;外链节点不触发(core 只服务库内目标);拖拽节点 / 框选 / 平移时按住鼠标键不弹预览(避免噪声) - 根节点连线起点改在节点右缘(
rootLineStartPositionKeepSameInCurve):与子层级一致、从右缘水平出发,消除此前「斜戳出来」的观感
修复
- 维基链接悬停预览链路修正:节点承载的链接读取顺序与图标分流同源——文档双链 / 嵌入走
mdWikiLinkpath、附件走attachmentUrl(并以mdAttachmentLinkpath为门控,避免已「移除引用」的残留字段让附件继续可悬停 / 可打开);hyperlink仅双链形态有效,裸 URL / 协议地址 / 畸形串一律不预览,与 Obsidian 阅读视图一致
行为变化提示
- 「纯双链化」:把文档 / 附件链接挂到节点时,节点文字无条件覆盖为链接显示名(别名优先),不再保留用户已有的手写正文;底层 md 行也只剩
[[目标|显示名]]。如果你此前依赖「正文节点只附加链接、保留手写正文」,请注意此行为变化 - 移除引擎自带 Ctrl+L 快捷键(仅
RESET_LAYOUT,不含「适应画布」、无提示):自动整理统一走mindmap-arrange命令——你把它绑到 Ctrl+L 时同样以「适应画布」收尾,口径统一
工程
- 新增
scripts/verify-visual.mjs可视化校验脚本(202 行),配合 CI 归档诊断日志(verify-visual-logs/,不入库) - 测试补强:新增
tests/mindmap-theme.test.ts,并对工具栏、维基链接、视图状态、保存管线、设置、节点操作、视口等大量用例扩充 - 对齐官方校验:obsidian-api(1.13.2 类型校验 0 错误)、eslint-plugin-obsidianmd(0.4.2,0 警告)
- 文档同步:README / AGENTS.md / 双语说明更新连线样式、「纯双链化」口径、悬停预览语义
兼容性说明
- 与 0.1.0 一致:需要 Obsidian 1.13.0+,仅桌面端(
isDesktopOnly: true) - 视图状态存储新增
lineStyle字段(按文件持久化连线偏好);旧版data.json无该字段时回落全局默认,向后兼容 .mindmap.md文件格式与命令 ID 均未变更;无破坏性格式变更
Added
- Connector style switching (toolbar + settings): the toolbar gains a "Line style" dropdown with
Auto/Curve/Direct/Elbow; the preference is persisted per file in the view-state store (never written into the note body). Settings adds "Default line style".Catalog/Timeline/Fishboneare natively straight layouts and cannot switch, so the control shows only "Auto" - Right-click menu "Reset zoom": a new "Reset zoom" item (rotate-ccw icon) restores the viewport to 100%
- Wikilink hover preview extended to three node kinds: document wikilinks / embeds (
mdWikiLinkpath) and dual-link / embedded / dragged vault attachments (attachmentUrlpaperclip channel) now trigger Obsidian's native page / attachment preview on hover; external-link nodes do not (core only serves vault targets); holding the mouse button during drag / box-select / pan suppresses the preview (avoids noise) - Root node connector now starts at the node's right edge (
rootLineStartPositionKeepSameInCurve): consistent with child levels and emits horizontally, removing the previous "skewed out" look
Fixed
- Wikilink hover-preview link resolution corrected: the node's link is now read in the same order as icon routing — document wikilink / embed via
mdWikiLinkpath, attachment viaattachmentUrl(gated onmdAttachmentLinkpathso a "removed reference"'s leftover field can't keep the attachment hoverable / openable);hyperlinkis only valid as a wikilink shape — bare URLs / protocol addresses / malformed strings never preview, matching Obsidian's reading view
Behaviour changes
- Pure-wikilink conversion: when a document / attachment link is attached to a node, the node text is unconditionally overwritten with the link's display name (alias first); user-authored body text is no longer preserved, and the underlying md line becomes only
[[target|display]]. If you previously relied on "body nodes only append a link and keep the hand-written text", note this change - Removed the engine's built-in Ctrl+L shortcut (only
RESET_LAYOUT, no "fit canvas", no notice): auto-arrange now goes solely through themindmap-arrangecommand — binding it to Ctrl+L still ends with "fit canvas", keeping one consistent behavior
Engineering
- New
scripts/verify-visual.mjsvisual-verification script (202 lines), with CI archiving its diagnostic logs (verify-visual-logs/, excluded from the repo) - Test hardening: added
tests/mindmap-theme.test.tsand expanded coverage for the toolbar, wikilinks, view state, save pipeline, settings, node actions and viewport - Aligned with official checks: obsidian-api (1.13.2 type-check, 0 errors), eslint-plugin-obsidianmd (0.4.2, 0 warnings)
- Docs synced: README / AGENTS.md / bilingual notes updated for line style, pure-wikilink semantics and hover-preview behavior
Compatibility notes
- Unchanged from 0.1.0: requires Obsidian 1.13.0+, desktop only (
isDesktopOnly: true) - The view-state store gains a
lineStylefield (per-file connector preference); olderdata.jsonwithout it falls back to the global default, so it is backward compatible - The
.mindmap.mdformat and command IDs are unchanged; no breaking format changes
0.1.0
0.1.0 是一次「先审计、后修复」的版本:先对照官方帮助与官方 API 类型定义做了 [[]] / ![[]] 支持度审计(结论与四项决策已登记),再逐条修复清扫中发现的七处静默数据损坏 / 保真缺陷,并把 Canvas / Bases 正式纳入文档类。核心能力(Markdown 无损往返、三类链接图标、图片与嵌入语法保真、PNG 导出、搜索、性能模式)保持兼容,且保真度整体提高。
新增
- Canvas / Bases 归为文档类:
![[画布.canvas]]/![[表格.base#视图]]与文档双链走同一通道——显示自绘文档页图标、可悬停预览、点图标打开;从文件浏览器拖入 canvas/base 同样按文档处理(.md用去扩展名写法,非 Markdown 文档必须带扩展名)。指向.base的链接此前会提示「无法预览」,现在可正常在标签页打开 - 非图片嵌入的管道位原文保留:
![[报告.pdf|300]]在编辑节点后不再丢|300。官方未定义该位语义(PDF 用#height=/#page=、音频无尺寸语法),故插件不解释、只原样保存并回写
修复
- 换文件时的保存排空可能写错文件(最严重):一次写盘的目标文件与其内容必须同源——排空期间切换文件(核心不等待
onUnloadFile)时,树快照与 frontmatter 一律按该次写盘的文件取,不同文件不再并入同一批次。此前可复现「新文件的正文写进旧文件」或「旧文件的 YAML frontmatter 被丢掉」 - 「清除链接」对嵌入无效、链接会复活:
![[笔记]]/![[报告.pdf|300]]在字段被清空后仍整行回写。现在只有指向本节点图片的嵌入才在检测中排除,文档 / 附件嵌入按链接形态处理,清除后一并剥离 - 外链 md 图片被改写成非法 wikilink:
在拖拽调宽或编辑文本后会写成![[https://…|300]](wikilink 目标不能是 URL → 图失效、alt 丢失)。现在外链判定先于「image等于解析期目标」分支,恒写 - 未编辑也会改动原文的两类字符:plain 行的行首缩进与行尾空格此前被 trim —— 4 空格缩进的代码块退化为普通段落,「行尾两空格 = 硬换行」被抹掉。现在
mdRaw保存未 trim 原文,节点显示文本仍 trim(行首缩进不进节点文本) - 跨文件夹移动后链接悬空:
[[folder/笔记]]+folder/笔记.md→other/笔记2.md此前改写为[[folder/笔记2]](目标不存在)。现在链接形态跟用户走(有前缀继续写路径)但路径取新位置;裸名链接保持裸名;canvas/base 的文件名保留扩展名 ---分两种身份:整行丢弃的规则会连 CommonMark 的 setext H2 下划线一起吞掉(标题\n---是二级标题 → 被降级为段落),而=====却保留,两者不对称。现在空行之后的短横线行仍是结构分隔线(丢弃,行为不变),紧跟非空行之后的按 setext 下划线保留
行为变化提示(保真度提高)
- plain 行现在逐字保真:行首缩进与行尾空格不再被吃掉。若你此前依赖「保存即归一缩进」,请注意行为变化(文件内容会更接近原文,这正是「无损往返」的承诺)
- 列表 / 标题之后的
---现在保留为独立分隔线,序列化器会补一个空行(此前整行消失) - 列表续行的行首缩进仍按树深度归一为 2 空格(内容不丢),只有行尾空格改为保真
工程
- 测试 1174 → 1200(38 个文件),新增
tests/constants.test.ts(33 例,锁定扩展名清单这类「漏登记即误判」的常量契约) - 每处修复都做负向对照:把修复退回旧实现,新用例必须变红(实测 3 / 3 / 4 / 6 / 2 / 3 条红),随后按 SHA256 校验复原
- 把 4 条「把旧缺陷当预期锁死」的用例改写为「已修复」(外链图片「已知降级」、plain 行 trim 归一、空列表项、续行 handle)
- 文档同步:
AGENTS.md与docs/markdown-mindmap-standard.md登记写盘归属不变式、---两种身份、行级保真规则、清除链接口径,以及[[]]/![[]]支持度审计结论与四项决策(均「保持现状」并写明复查触发条件)
兼容性说明
- 与 0.0.x 一致:需要 Obsidian 1.13.0+,仅桌面端(
isDesktopOnly: true) .mindmap.md文件格式与命令 ID 均未变更;无破坏性变更。本版的行为差异只有「保真度提高」与「---不再丢行」两类
Added
- Canvas / Bases are now document-class targets:
![[Board.canvas]]/![[Table.base#View]]share the document-wikilink channel — the hand-drawn document icon, hover preview and click-to-open all behave like note links; dragging a canvas/base file in from the file explorer also treats it as a document (.mddrops the extension, non-Markdown documents must keep it). Links to.basefiles used to report "cannot preview"; they now open in a tab - Pipe content of non-image embeds is preserved verbatim: editing a node no longer drops
|300from![[report.pdf|300]]. The official docs never define that slot (PDFs use#height=/#page=, audio has no sizing syntax), so the plugin stores it as-is and writes it back unchanged
Fixed
- The save drain could write to the wrong file when switching files (most serious): a write's target file and its content must belong to the same file — during the drain, snapshots and frontmatter are now taken for the file being written and different files never share a drain batch (core does not await
onUnloadFile). It was reproducible that the new file's body was written into the old file, or the old file's YAML frontmatter was dropped - "Clear link" did nothing for embeds, so links came back:
![[note]]/![[report.pdf|300]]were still written verbatim after the link fields were cleared. Only embeds of the node's own image are excluded from the check now; document/attachment embeds are treated as links and stripped - External markdown images were rewritten into invalid wikilinks: after resizing or editing text,
became![[https://…|300]]— a wikilink cannot point at a URL, so the image broke and the alt text was lost. The remote-URL check now runs before the "image equals its parsed target" branch, always writing - Two kinds of characters were changed even without editing: leading indentation and trailing spaces of plain lines were trimmed — a 4-space indented code block degraded into a paragraph, and the "two trailing spaces = hard line break" was erased.
mdRawnow keeps the untrimmed source while the node's display text stays trimmed - Links went stale after moving a file across folders:
[[folder/note]]withfolder/note.md→other/note2.mdwas rewritten to[[folder/note2]](a dangling link). The link now keeps the user's shape (path stays a path) but takes the new location; bare links stay bare, and canvas/base names keep their extension ---has two identities: the drop-the-whole-line rule also swallowed CommonMark setext H2 underlines (title\n---is an H2 heading and was downgraded to a paragraph) while=====survived — an asymmetry. A dash line after a blank line is still a structural separator (dropped, unchanged); one directly after a non-blank line is now kept as a setext underline
Behaviour changes (higher fidelity)
- Plain lines are now verbatim: leading indentation and trailing spaces are no longer eaten. If you relied on "save normalises indentation", note the change — file content now stays closer to the source, which is what lossless round-trip promises
- A
---after a list or heading is now kept as a standalone separator (the serializer inserts a blank line); previously the whole line disappeared - Continuation lines inside list items still normalise their leading indentation to 2 spaces per tree depth (nothing is lost); only trailing spaces became verbatim
Engineering
- Tests 1174 → 1200 (38 files), including a new
tests/constants.test.ts(33 cases) locking down constant contracts such as the openable-extension list, where a missing entry means a wrong "cannot preview" - Every fix carries a negative control: reverting the fix to the old implementation must turn the new cases red (measured 3 / 3 / 4 / 6 / 2 / 3 failures), then the file is restored and verified by SHA256
- Four tests that had locked in old defects as expected behaviour were rewritten as "fixed" (external-image "known degradation", plain-line trimming, empty list item, continuation handling)
- Documentation:
AGENTS.mdanddocs/markdown-mindmap-standard.mdnow record the write-ownership invariant, the two identities of---, the plain-line fidelity rules, the clear-link semantics, and the[[]]/![[]]support audit with its four "keep as-is" decisions and their recheck triggers
Compatibility notes
- Unchanged from 0.0.x: requires Obsidian 1.13.0+, desktop only (
isDesktopOnly: true) - The
.mindmap.mdformat and command IDs are unchanged; nothing is breaking. The only behaviour differences are higher fidelity and---no longer disappearing
0.0.5
0.0.5 是一次以「代码检查 + 实测复现」驱动的修复版本:五路并行审查(官方 API 类型定义、官方 eslint 规则、源码正确性、发布/工作流一致性、测试缺陷检测力)后,逐条修复了三处 Markdown 往返数据丢失、悬停预览弹窗定位、改名断链、多窗口(popout)兼容与若干生命周期问题,并删除了 28.8 KB 无用 CSS。核心能力(Markdown 无损往返、三类链接图标、图片、PNG 导出、搜索、性能模式)保持兼容。
新增
F2编辑当前节点:与双击等效;输入框内让位给输入,已在编辑时忽略,无激活节点时仍吞键(避免触发核心「重命名文件」)。引擎自带的 F2 因要求「事件目标为document.body且指针在画布内」而时常失效,现由视图 scope 统一接管- 「重置缩放(100%)」按钮(位于「适应画布」左侧):以画布中心为锚点回到 100%,屏幕可见内容保持原位;自动整理后同样重置缩放(不再把大图压到文字不可读)
- 打开时的默认视口:100% 缩放 + 整体内容包围盒居中(不再 fit 压小,也不再只居中根节点导致偏心树偏到一侧)
修复
- Markdown 往返不再丢内容(实测复现后修复,新增 8 例回归用例):
- 图文混合 / 「图 + 链接」节点只要编辑过文本,图片引用会被静默丢弃(合成路径此前只输出一枚 token);给已有链接的节点插入图片、给已有图片的节点插入链接同样丢图 —— 现按原文顺序写出全部 token
- 移除图片后旧图会复活(
rawOk在image为空时跳过图片检查,而「链接已清除」检测又排除嵌入语法)—— 现判定「image 空但 md 图片字段仍在 → 已变更」,并同步清理mdImageTarget/mdImageWidth/mdImageHeight/mdImageAlt - 节点文本恰好含同名串时,新插入的图片/链接不会写入文件(特征用裸子串判断)—— 现按嵌入/双链语法边界匹配
![[图.png|说明]]的说明文本在编辑节点后丢失 —— 现解析与回写均保留
- 悬停预览弹窗现在会翻转到节点下方:官方
HoverPopover.position()的锚定矩形是混合取值(宽高走targetEl.offsetWidth/offsetHeight、位置走getBoundingClientRect()),SVG 节点没有前两个属性 →bottom/right为NaN,官方定位函数「下方放得下就放下方」的分支恒假,且上方放不下时top被写成"NaNpx"(表现为完全没有预览)。现触发前补齐只读几何,恢复「下方优先、下方不足翻上方、两侧都不足则限高滚动」 - 编辑中心节点改名不再断链:改用
FileManager.renameFile(Vault.rename只改文件系统、不更新库内其他笔记中指向本文件的链接/反链) - 多窗口(popout)兼容:拖拽换父、图片拖拽调宽会话与窗口级粘贴兜底的监听改挂画布所属窗口(此前在 popout 里图片调宽完全失效、会话悬挂到视图关闭)
- 生命周期与竞态:引擎初始化的 rAF 可取消(关闭视图后不再建出无人销毁的引擎实例)、视图关闭后不再应用过期加载结果、首帧视口恢复定时器随销毁取消、跨
await校验引擎代际(弹窗/保存图片期间换文件不再静默丢失编辑) - 文件名清理对齐 Obsidian 约束:补
# ^ [ ](含这些字符的名字无法被双链正确引用)、结尾点/空格与CON/PRN/…保留名(此前会直接失败或写出无法引用的文件名) - 文档纠错:README 曾写「编辑节点文本 = 双击或按 Enter」——Enter 实际是「添加同级节点」(按下去会新建节点),现改为 F2 并补上「重置缩放」;同时修正若干过期注释
工程
- 删除无用代码 −28.8 KB:
styles.css的 vendor 段 100% 是 Quill 富文本样式(240 条.ql-*选择器),而本插件从不注册 RichText 插件、引擎样式由 bundle 运行时注入document.head—— 删除该段及vendor/simple-mind-map.css、scripts/sync-vendor-css.mjs与对应 npm 脚本;实测删除前后渲染契约与节点测宽逐字节相同 - 测试 430 → 472(35 个文件):新增默认视口真实几何(此前只断言 mock 被调用)、快捷键注册接线、popout 窗口监听归属、文件名清理、保存「卸载兜底快照」等用例;变异检验确认新用例能抓住旧缺陷
- CI 增加
verify:visual --require-chrome(浏览器缺失即失败,不再静默跳过);发布工作流的 tag 版本校验前移到构建之前(fail-fast) - 收敛重复实现:「插入承载节点」4 处手写收敛为单一入口
insertChildNodeWithData;三处裸getData改走防腐收口getNodeDataString
兼容性说明
- 与 0.0.1–0.0.4 一致:需要 Obsidian 1.13.0+,仅桌面端(
isDesktopOnly: true) - 本版无破坏性变更;
.mindmap.md文件格式与既有文件的解析/回写行为保持兼容
Added
F2edits the active node (same as double-click): text inputs keep F2, an in-progress edit is left alone, and the key is still swallowed with no active node (so core's "Rename file" never fires). The engine's own F2 only fires when the event target isdocument.bodyand the pointer is inside the canvas, which is rarely true — the view scope now owns it- Reset zoom (100%) button (left of Fit to canvas): returns to 100% anchored at the canvas centre so the visible content stays put; auto-arrange resets zoom too instead of fitting the whole map down to unreadable text
- Default viewport on open: 100% zoom with the whole content bounding box centred (no more fit-to-canvas shrink, and no more root-only centring that pushed lopsided maps to one side)
Fixed
- Markdown round-trip no longer loses content (reproduced, then fixed, with 8 new regression cases):
- editing the text of a node that has both an image and a link silently dropped the image (the compose path emitted a single token); adding an image to a linked node or a link to an image node lost the image the same way — all tokens are now written in source order
- removing an image brought it back on the next save (
rawOkskipped the image check whenimagewas empty while the "link cleared" check excluded embeds) — a removed image is now detected, andremoveNodeImageclears the md image fields - a newly inserted image/link was not written when the node text happened to contain the same string (bare-substring feature match) — matching is now embed/wikilink-boundary aware
- the caption in
![[img.png|note]]was lost after editing — now preserved on parse and write-back
- Hover previews now flip below the node: the official anchor rect mixes sources (
targetEl.offsetWidth/offsetHeightfor width/height,getBoundingClientRect()for position); SVG nodes lack the former, sobottom/rightbecameNaN, the "place below when there is room" branch never applied, and with no room abovetopwas set to"NaNpx"(no preview at all). The geometry is now supplied before the preview is requested - Renaming the central node no longer breaks links: uses
FileManager.renameFile(Vault.renamerenames on disk only and leaves links/backlinks pointing at the old name) - Popout window support: drag-reparent, image-resize sessions and the window paste fallback now listen on the canvas's own window (image resizing used to fail outright in a popout and the session hung until the view closed)
- Lifecycle and races: the engine-init rAF is cancellable (no orphan engine instance after the view closes), stale load results are never applied, the first-frame viewport timer is cancelled on destroy, and engine identity is re-checked across
await(switching files mid-dialog no longer drops edits) - File-name sanitising matches Obsidian's rules:
# ^ [ ], trailing dots/spaces and reserved names (CON/PRN/…) are handled — previously they either failed outright or produced names that cannot be linked to correctly - Docs: the READMEs claimed "double-click or press Enter" to edit a node — Enter actually inserts a sibling node. They now say F2 and also list Reset zoom
Engineering
- 28.8 KB of dead code removed: the
styles.cssvendor block was 100% Quill rich-text CSS (240.ql-*selectors) while the plugin never registers the RichText plugin and the engine injects its own styles at runtime — the block,vendor/simple-mind-map.css,scripts/sync-vendor-css.mjsand its npm script are gone; rendering metrics are byte-identical before and after - Tests 430 → 472 (35 files), including real geometry for the default viewport (the only previous "coverage" asserted that a mock was called), hotkey wiring, popout listener ownership, file-name sanitising and the save-time fallback snapshot; mutation checks confirm the new cases fail against the old code
- CI runs
verify:visual --require-chrome(a missing browser now fails instead of silently skipping); the release workflow verifies the tag againstmanifest.jsonbefore building - Duplicate owners collapsed: four hand-written
INSERT_CHILD_NODEcall sites becameinsertChildNodeWithData, and three rawgetDatareads now go through thegetNodeDataStringfacade
Compatibility notes
- Unchanged from 0.0.1–0.0.4: requires Obsidian 1.13.0+, desktop only (
isDesktopOnly: true) - No breaking changes;
.mindmap.mdfiles and their parsing/write-back behaviour remain compatible