# Changelog

All notable changes to this project are documented here. Release-specific notes are also published on GitHub Releases.

## [Unreleased]

### Added

- Add 复制当前节点文本内容 immediately above 复制节点路径 in node context menus. It copies the current node's complete own text without its path, ancestors, or descendants, preserves line breaks, and reuses the existing clipboard error and busy-state handling without modifying the mindmap or chat draft.
- Node context menus gain 复制节点路径: copy the complete root-to-current path as single-line plain text joined by `--->`, without descendants, chat submission, or draft replacement. The action uses the existing text clipboard fallback, disables menu actions while pending, and keeps errors visible for retry.
- Default generated names now include the user-facing host tool: `YYYYMMDD-HHmmss-DSH-<description>.md`. The settings panel adds 默认脑图命名是否添加AI工具标记 (`includeAIToolMarker`, on by default); disabling it restores the unmarked format without changing existing files or explicit user titles. The marker is DSH regardless of the selected model/provider. Changes apply to the next naming plan, while a create already awaiting confirmation keeps the exact path shown to the user.
- Refresh current mindmap from disk in either panel mode, without replacing drafts or submitting an AI request; failed reads retain the current canvas and show an error.
- A reproducible `npm run bench:client` benchmark compares cached and full replay using fictional long-conversation data and verifies output equivalence before timing.

- Whole-map overview (全图): the canvas zoom bar gains a 全图 button beside 适配. It uses both viewport dimensions and can zoom below the readable floor, but stops at 25%; exceptionally large maps may still overflow and remain scrollable. Like a manual zoom it stops automatic re-fitting, so the observer cannot pull the view back to the readable floor; 适配 or switching documents resumes auto-fit.

- Default mindmap inbox and naming (036): a request without a location ("创建一个脑图", "把刚才的讨论整理成脑图", "盘点一下这个问题") now files the document in `.mindmaps/` as `YYYYMMDD-HHmmss-中文描述.md` instead of dropping it on top of the session working directory root. `mindmap_create` takes `name` when the user named the mindmap and a short `description` otherwise — the host reads the clock, so the model never guesses a time. Descriptions are whitespace-collapsed, truncated to 24 characters, and rejected outright when they carry path separators or control characters. `.mindmaps/` is created on first use (a same-named ordinary file, a symlinked-out directory, and a missing session cwd all fail closed), is never added to `.gitignore`, and shows in the directory tree as 脑图收件箱（.mindmaps） with the root/blank-space context menu offering 在脑图收件箱新建脑图. An explicit `directory` is still honoured verbatim, and the approval hook now computes the final normalized path before the first write, so a fresh inbox creation still goes through the normal confirmation. The root node title keeps equaling the filename, so a default-created mindmap temporarily shows its timestamp as the root title; splitting filename from display title is left as a separate decision.
- Approval and write now agree on one path (036 follow-up): the confirmation prompt resolves the first free `-2`/`-3` candidate before the user is asked, instead of naming a file a previous same-second capture had already taken and then silently writing a sibling. If another create claims the confirmed name during the deliberation window, the host steps to the next free suffix, the tool result reports that final path, and the session grant is recorded against it rather than the stale candidate — so an existing file is never overwritten and a confirmation never buys a write nobody was shown. Concurrent default creates are verified end to end: eight same-description creates launched in parallel produce eight distinct documents with no lost bytes.

- Node search and quick navigation: ⌘/Ctrl+F (or the 🔍 in the canvas zoom bar) opens a compact search over the current mindmap — case-insensitive substring matching on node text, a `1 / 5` counter, and Enter / ↓ / ↑ (Shift+Enter) stepping through matches with wrap-around. Every match gets a light theme-token ring and the active match a stronger double ring; jumping keeps the user's zoom, scrolls the match to the focus anchor, and auto-expands collapsed ancestors so hits inside folded subtrees are actually revealed. Search is pure view state (no markdown writes, no revision changes), reconciles on stable node ids after an AI re-render, and resets on document switch; the ⌘/Ctrl+F interception only fires while the mindmap canvas is active and never while the caret sits in another text entry.

- Canvas focus experience: clicking a node now animates the zoom/scroll transition (≤250 ms, eased) and caps each click's zoom change at ×2/÷2, so focusing into a huge map drills down progressively instead of jumping 4× in one step. The transition interpolates the node's viewport anchor from its clicked position to the left-quarter focus point (no first-frame jump), and animation frames are written directly to the canvas DOM layer without re-rendering the tree per frame. Any new interaction (drag, zoom buttons, another click) interrupts the animation instantly, and the animation self-disables for the session once the resize-observer circuit breaker trips, falling back to the previous instant behavior. When an AI edit pushes the selected node fully out of view, the canvas performs a minimal scroll to bring it back to the edge without touching the user's chosen zoom.
- Copy as markdown: a 复制全文 button now sits left of 导出图片 in both the sidebar toolbar and the standalone panel header. One click copies the current mindmap's raw markdown source to the system clipboard via the existing `copyPlainText` helper (Clipboard API with a textarea fallback) — the document state's `doc.content` is used directly instead of re-serializing the tree, so headings, nested lists, tables, and the original whitespace round-trip losslessly. Success briefly switches the button label to 已复制 ✓; failures surface in the shared export error slot. The node context-menu 复制全文 (single-node text) is unchanged.
- Narrow panels (sidebar mode, viewport under 400px) now fit the mindmap by height instead of width; the overflowing part is reachable by panning, which keeps text readable in the 280px-minimum sidebar. The canvas scroller also reserves a stable scrollbar gutter (`scrollbar-gutter: stable`), so the fit ratio no longer oscillates as scrollbars appear and disappear; hosts without the property degrade to the previous behavior.
- Read-only document opening now renders a clicked Markdown file before asking the AI to take over editing. `mindmap_create` accepts an optional relative directory, and read/write results carry a SHA-256 revision so stale `mindmap_update` calls can be rejected instead of overwriting newer edits.
- Standalone panel width now remains usable on narrow viewports by clamping the initial, saved, resized, and settings-derived widths together.
- Write approval now supports `per-operation`, `session` (default), and `off` for ordinary writes. Session mode reuses a successful approval per document, while renames, empty-content deletions, and broad rewrites stay gated; the settings page exposes the policy and the current mindmap workspace shows and revokes the current-session grant.

### Changed

- Mindmap guidance and create/open commands now route directly to native `mindmap_*` tools, explicitly excluding skill discovery/loading (`find-skills`, `workbuddy-mindmap`), external scripts, and generic file-write substitutes.
- Share the document projection between the header and workspace, and cache tool-result JSON per session. Changed result text invalidates its entry; session switches discard the previous reducer.

- Node text size is now one constant (`NODE_FONT_SIZE = 13`) with no smaller overrides: code blocks, tables and inline code used to render at 12px, which broke the readable-floor promise — the whole canvas shares a single CSS zoom, so 12px text landed at ~11.1px at the 12/13 floor. Code keeps its monospace face and surface colour, so the hierarchy survives without the size step.

- Automatic fit and node focus now keep 13px node text at an effective size of at least 12px. Large maps or subtrees may extend beyond the viewport and remain accessible by scrolling/panning; manual zoom still supports 25%–300%. Focusing after a manual zoom below the readable floor restores readability in one step, even when that exceeds the usual ×2 jump limit.
- `mindmap_create` without `directory` no longer writes to the root of the session working directory: the file goes into the `.mindmaps/` inbox (see above). Existing documents stay exactly where they are — only new creations move.

### Fixed

- Extend the full-file-access compatibility fix to root renames, clearing content, and broad rewrites: when the host's live `sandboxPolicy.resolve({ session })` explicitly returns `danger-full-access`, these operations no longer request an extra plugin approval that the full-access preset would automatically reject. This supersedes the high-risk exception in the earlier ordinary-write fix below. Non-full-access policies still gate high-risk writes, and disabling approval prompts alone grants no file access. Upstream approval decisions, path containment, collision checks, size limits, revision conflicts, and session-grant isolation are preserved; guidance, settings help, and security documentation now describe the same boundary.
- Ordinary create/update calls no longer request an extra plugin confirmation when the host's live per-session `sandboxPolicy.resolve({ session })` explicitly returns `danger-full-access`. DSH's full-access preset also disables approval prompts, which rejects an extra `ask` as “the user rejected tool” even without a manual rejection. Full-access calls do not create reusable document grants, the workspace reports the effective confirmation mode, and effective-mode changes invalidate that session's prior grants and pending approval results without affecting other sessions. Switching back to a restricted policy restores the configured confirmation behavior. Renames, cleared content, broad rewrites, and upstream denials/asks/cancellations remain gated; missing, failing, or unknown policy APIs retain the previous confirmation policy. No session permission or host configuration is changed.
- Read draft text and attachment state from the host's `useInput` snapshot, including edits made while a file request is pending. Unknown state preserves input; old action-face getters remain supported. Inline references and attachment-only drafts block automatic submission too.
- Fresh disk reads no longer lose to older tool snapshots. A later tool event takes over normally; overlapping reads, closing, session switches, and newer open intents invalidate stale requests.
- Resolve all mindmap HTTP calls against the page base address, retaining reverse-proxy prefixes while refusing cross-origin base URLs.

- A pending mount-time auto-fit frame no longer overrides an early 全图 click or other manual zoom choice; the frame still runs when the user has not changed the view.
- Keep the readable zoom floor finite by deriving it from the same numeric font-size constant used by node boxes, rather than parsing a CSS style string at startup.
- Restored host-side settings on 0.1.7+ hosts: the service no longer has `register`, so the 0.15.0 startup path never read live values or ran its `.imported` migration. The flat `Config` export remains the loader-discovered schema; host approval now reads the `mindmap` descriptor from `describe()` at execution time, and legacy settings are restored via revision-checked `update()` only when the profile has no saved user overrides. Older hosts retain their `register` path. The tests now mock the actual service surface without `register` and exercise live approval changes and one-time migration.
- Extended the tooling peer range with `^0.2.0-alpha.0` for the 0.2.0 prerelease tuple. Hosts that enforce peer compatibility no longer skip the plugin solely because its previous range stopped at 0.1.7; the optional sidebar peer is unchanged.
- Extended the tooling peer range with `^0.2.1-alpha.0` for the 0.2.1 prerelease tuple: hosts on `0.2.1-rc.x` (npm latest) pass the enforced peer check again instead of skipping the plugin. The 0.2.1 API surface was verified face-by-face against the `0.2.1-rc.2` package and is identical to 0.2.0 — settings methods, `Agent { id }`, defineTool/execute, `ChatSnapshot.legacy.nodes`, `useChat`, the three slots and every client inject module survive unchanged; 0.2.1 itself only adds new-model and Search/Fetch API changes. The optional `dsh-better-sidebar` peer is unchanged.
- Extended the tooling peer range with `^0.2.2-alpha.0` for the upcoming 0.2.2 prerelease tuple, as a pre-emptive measure while 0.2.2 is still on the alpha channel (`0.2.2-alpha.2`): following the established one-comparator-per-tuple pattern, the range now covers the `[0,2,2]` tuple ahead of time, so hosts on 0.2.2 prereleases pass the enforced peer check instead of being skipped the moment 0.2.2 ships — stabilising 0.2.2 needs no further change here. Pure metadata: no runtime code, tests, README, settings page, or devDependencies are touched. The optional `dsh-better-sidebar` peer is unchanged.
- 0.1.7 settings volatile mechanism: all seven Config fields (`requireApproval`, `approvalMode`, `defaultPanelWidth`, `lineStyle`, `cardStyle`, `colorTheme`, `growthAnimation`) are now marked `.extra('volatile', true)` on their schemastery schema nodes. Without `meta.volatile`, the 0.1.7 host's settings storage (`dsh-settings` `volatileForm`/`isVolatilePath`/`write`) rejects all reads and writes for the `mindmap` namespace — the settings panel (设置 → 思维脑图) displays stale or default values and saving a new choice is silently dropped. The `volatile` meta is transparent to older hosts (0.1.5 and below ignore it), so the dual-path behavior is unchanged. A one-time legacy migration was also added: on startup, when the settings value is all-defaults, the plugin reads `~/.dsh/settings.yaml.imported` (the host-renamed old `settings.yaml`), extracts the `mindmap:` section's scalar fields with a line-by-line parser (no YAML dependency), validates each value against its allowed set, and writes the result to the new settings store via `scope.update()`. The `.imported` file is never deleted (it is host-managed); failures are silently skipped so the plugin still loads and the user can configure manually.
- dsh-tools peer dependency window extended to cover the `0.1.7-rc.2` release: `^0.1.7-alpha.0` is appended to the comparator list (`^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.3-alpha.0 || ^0.1.5-alpha.0 || ^0.1.7-alpha.0`). Under node-semver prerelease tuple rules, `0.1.7-rc.2` requires a `[0,1,7]` tuple with a prerelease tag in the range — without this comparator, `pnpm add dsh-mindmap` on a 0.1.7 host fails with ERESOLVE. The static API surface (Agent `{ id }`, `SessionHeader.cwd`, `settings.describe` envelope, `ChatSnapshot.legacy.nodes`, `ToolResultNode` fields, `conversation.session.header.actions` / `conversation.chat.turn` slots, `settings.section` slot, client inject ids, `@deepseek-ai/schemastery ^3.18.0`) was verified unchanged against the 0.1.7-rc.2 npm package. A `settings.plugin.item` string found in client.js comments was confirmed to be a documentation remark (the actual registration uses `settings.section`, which remains healthy in 0.1.7); no code migration is required.
- Creating a mindmap re-checks its target against the filesystem immediately before the write (036 follow-up, P1). The path was validated when the confirmation was drawn, but the plan was then trusted as-is: renaming the target directory and replacing it with a symlink pointing outside the session working directory while the user was reading the confirmation made `mindmap_create` write the file outside the workspace. Every candidate — inbox or explicit directory — is now re-resolved through the same inside-the-cwd check right before its atomic `wx` create; a path that no longer resolves inside the session cwd aborts the create (no outside file, no session grant), and the user is asked to start it again. The path confirmed and the path written stay the same one.
- Fixed a session-initialization race where an AI `mindmap_create` could expand the right-side tab but leave the workspace on the directory list. Session cleanup now runs before automatic document selection, and the opening-event cursor is reset when switching sessions.
- The broad-rewrite ratio check now applies only to documents of 4 KB or more, so rewriting a few-hundred-byte mindmap end to end no longer escalates to a fresh high-risk confirmation and the session grant stays usable. Renames, cleared content, and removed blocks above 16 KB remain gated.
- Choosing an explicit confirmation frequency in the settings page now lifts the legacy `requireApproval: false` switch. That switch takes precedence over `approvalMode` in the engine, so the previous behaviour saved the choice and then reverted it to `off`.

## [0.11.0] - 2026-09-11

### Fixed

- dsh-tools peer dependency window extended to cover the `0.1.5-rc.1` release: `^0.1.5-alpha.0` is appended to the comparator list (`^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.3-alpha.0 || ^0.1.5-alpha.0`). Under node-semver prerelease tuple rules, `0.1.5-rc.1` requires a `[0,1,5]` tuple with a prerelease tag in the range — without this comparator, `pnpm add dsh-mindmap` on a 0.1.5 host fails with ERESOLVE. The static API surface (Agent `{ id }`, `SessionHeader.cwd`, `settings.describe` envelope, `ChatSnapshot.legacy.nodes`, `ToolResultNode` fields, `conversation.session.header.actions` slot, client inject ids, `@deepseek-ai/schemastery ^3.18.0`) was verified unchanged against the 0.1.5-rc.1 npm package.
- Runtime verification on dsh 0.1.5-rc.1 with `dsh-better-sidebar` v0.18.1 confirms the plugin loads and renders in sidebar Tab mode: the MindmapSidebarTab registers, the mindmap settings panel (line style, card style, color theme, default width, growth animation), the directory tree tab, and the header 思维脑图 button all render and function correctly. A known timing issue causes the first directory-tree fetch to report "session has no working directory" on session switch (sessionStore write races the first fetch); clicking 刷新 restores it.
- Sidebar auto-expand: clicking the header 思维脑图 button (or an AI `mindmap_create`/`open` auto-open) now automatically expands the Better Sidebar panel in addition to focusing the mindmap tab. Previously the tab activated but the panel stayed collapsed, requiring a manual "展开侧边栏" click. Root cause: `dsh-better-sidebar`'s `openTab(seed, scope)` only auto-expands the panel for "content-type" opens where the seed carries a `path` or `url` field (`service.ts:707`); a pure type-only open (`{ type: "dsh-mindmap:mindmap" }`) never expands. Fix: a new `openMindmapTab(svc, scope)` helper attaches an inert `url: "dsh-mindmap://mindmap"` to the seed, making it a content-type open. All three `openTab` call sites (header button click, MindmapSlot auto-open fallback, MindmapSidebarTab onAutoOpen) go through the helper. The `url` is not overwritten onto an already-existing tab (focus path), so there are no side effects; older BS versions without this logic degrade gracefully to the previous behavior (tab activates without panel expansion).

## [0.10.1] - 2026-09-10

### Added

- Better Sidebar native tab coexistence: when `dsh-better-sidebar` is installed, the mindmap registers as a single-instance tab (`dsh-mindmap:mindmap`) inside Better Sidebar instead of rendering its own floating panel. The in-tab toolbar is a single compact row (mindmap list, current mindmap, export button on the same line), labeled 脑图列表; the header 思维脑图 button opens or focuses the tab. When Better Sidebar is absent, the standalone floating panel with drag-resizable width and layout-push CSS remains unchanged (labeled 目录). `dsh-better-sidebar` is declared as an optional peer dependency — no duplicate instance is bundled. The mode switch is fully reversible via a `sidebarBus`-driven reversible effect: if Better Sidebar is unloaded mid-session, the standalone panel and layout-push CSS are restored automatically. A session-scoped data bridge (`sessionStore`) passes header-slot-captured `nodes`/`inputActions` to the tab component, with cleanup on session switch and component unmount.

### Fixed

- 内嵌 M 徽标增大至 16px、字母加粗，改为透明底与跟随文件名主题色的描边，改善换肤后的辨识度。
- 内嵌模式的 Markdown 文件恢复为紧凑 M 徽标；其他格式文件仅展示，禁用悬停反馈、打开、拖拽与右键操作，文件夹仍可展开。
- 内嵌 Better Sidebar 的列表与标签采用宿主字体角色（正文 14px、界面 12px），目录改用 14px 线框图标，修复字号继承偏大及标签字号混杂；独立模式和脑图节点排版不变。

## [0.10.0] - 2026-09-09

### Added

- Collapsible subtrees: every node with children shows a small toggle on its connector. Collapsing hides the subtree and reports how many nodes are hidden, which keeps large maps navigable. The state is view-only — the markdown file is untouched and image export still covers the full subtree. Collapsed nodes reset when the document changes, and stale ids are pruned after the AI rewrites the tree.

### Fixed

- 画布拖拽不再吞掉折叠开关等控件的点击。
- 目录打开/焦点同步：宿主草稿 API 不可读时不再静默放弃；有非空草稿时指令转入剪贴板。

## [0.9.0] - 2026-09-09

### Fixed

- Settings now wait for a late-arriving client connection instead of permanently capturing its initial absence. The client also supports direct settings descriptors and the current positional update API, while retaining the legacy envelopes.
- Canvas dragging remains available horizontally and vertically when the map fits inside the viewport. Native scrolling is used first; bounded offset compensation continues the drag at an edge, and Fit or node focus returns the map to center.
- Mindmap file access now fails closed when the session working directory is unavailable. Reads are limited during streaming rather than trusting a pre-read size check, and updates use a same-directory temporary file plus atomic replacement to avoid partial writes.
- Pathological Markdown tables now have row, column, and total-cell limits. PNG export rejects oversized canvas allocations instead of risking a browser crash.
- Switching sessions clears pending mindmap state and ignores stale directory responses; repeated resize events no longer disable automatic canvas fitting.
- Writes now require native approval by default. Directory opening and focus synchronization never overwrite a non-empty or unreadable chat draft.
- The tool guidance no longer implies that a placeholder node needs a trailing space: any list item with no text after the marker becomes a placeholder.

## [0.8.2] - 2026-09-09

### Added

- Read-size guard for `mindmap_open` / `mindmap_get`: both tools `stat` the file first and refuse to read anything larger than 2 MB (`MAX_READ_BYTES`, the same cap as writes), so opening an accidentally huge document can no longer flood the model context.

### Fixed

- Export table rendering: the per-cell wrap width is clamped to at least 12px (`tableCellMinInner`, one full-width character) in both box measurement and SVG drawing, so extremely wide tables (e.g. 60 columns) can never degenerate into one-character-per-line stacking.

### Changed

- Changelog realignment: entries that had shipped with 0.5.0–0.8.1 are moved from `[Unreleased]` into their own version sections, split by the real git tag boundaries; README and README.zh-CN updated to reflect the dual dsh compatibility (`0.1.1-rc.2` + `0.1.2-rc.1`), paragraph promotion (019 block concept), and the published release tag.
- Dependabot: major-version upgrades of `actions/checkout` and `actions/setup-node` (≥5) are ignored until evaluated separately (the two hanging PRs were closed).

## [0.8.1] - 2026-09-08

### Added

- `@deepseek-ai/dsh-tools` peer dependency with a four-range version window (`^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.3-alpha.0`), so plugin installs resolve cleanly across the harness tooling line.

### Changed

- Dependency refresh (`@deepseek-ai/cosmokit` 1.8.2 → 1.8.3, `@deepseek-ai/schemastery` 3.18.1 → 3.18.2, lockfile updated accordingly); `.gitignore` also ignores `_*` and `._*` at the repository root.

## [0.8.0] - 2026-09-04

### Fixed

- Compatibility with dsh 0.1.2-rc.1 (statically verified, docs/023), while keeping 0.1.1-rc.2 working — all fixes are dual-path with the old path first:
  - **Host tools**: `Agent` no longer carries a live `session` (it is reduced to `{ id }`), so `sessionCwd` now falls back from `exec.agent.session.header.cwd` (dsh ≤0.1.1) to looking the session up via `ctx.sessions.get(exec.agent.id).header.cwd` (0.1.2-rc.1). Without this, every tool failed with "the session has no working directory" on 0.1.2-rc.1.
  - **Live panel data**: the session snapshot no longer carries a flat `nodes` array (conversation content moved to the Chat view), so the panel now reads nodes via `useChat → ChatSnapshot.legacy.nodes` (0.1.2-rc.1+) first and falls back to `useSession → SessionSnapshot.nodes` (dsh ≤0.1.1). `ToolResultNode` field names are unchanged across both, so document replay, auto-open, and error surfacing work as before.
  - **Settings panel**: `settings.describe` over the client connection now returns the descriptor array directly (0.1.2-rc.1+) instead of an aggregate `result.value.namespaces` (dsh ≤0.1.1); both envelope shapes are parsed.
  - **Hygiene**: `dsh.client.inject` additionally lists `@deepseek-ai/dsh-cordis-client-runner` (the 0.1.2-rc.1 browser runtime module id; `@deepseek-ai/dsh-client-runtime` is kept for 0.1.1-rc.2 — a missing id is silently skipped by the loader, so both coexist safely).

## [0.7.0] - 2026-08-31

### Fixed

- A code-review sweep (`_issues/002`) landed 11 fixes, each with a failing regression test first:
  - **Markdown parsing**: an unclosed `---` frontmatter fence at the top no longer swallows the whole document into an empty tree; the root-title echo merge (`firstTopH1Seen`) is set only by a top-level H1 so an earlier H2 or a heading inside a quote block no longer suppresses it; GFM table column count is pinned to the **separator row** (short rows padded, extra cells dropped) instead of the header; fenced code blocks close only on a matching fence type (`~~~` is no longer ended by a ``` line) and `parseTableRow` honors an escaped `\|` preceded by an escaped backslash (`\\|`).
  - **Document snapshots**: `mergeDocuments` only deletes a dropped local entry when no live snapshot owns the same path, so a recreated file that a snapshot also references keeps its panel openable.
  - **Link rendering**: the bare-URL pattern no longer swallows CJK punctuation/full-width characters (`https://a.com，然后…` stops at the comma); `[text](url)` and bare links render as plain text unless the scheme is http/https/mailto (blocking `javascript:`/`data:`), and `openLink` calls `preventDefault` only when `window.open` actually succeeds, so host-blocked popups fall back to default navigation instead of a dead click.
  - **Host safety & approval**: the tree route and all mindmap path resolutions now reject symlink escapes out of the working directory via a `realpath` walk-up (`resolvesInsideBase`); `requireApproval` gates `mindmap_create` as well as `mindmap_update`; and `listDirectoryLevel` stops iterating once it hits the entry cap (perf) while still flagging truncation.
  - **PNG export**: wide tables measure their box height using the same clamped per-column width the renderer uses, so wrapped cell text no longer overflows the measured box bottom.

## [0.6.0] - 2026-08-30

### Added

- Canvas panning (021): the mindmap canvas can now be dragged around with the middle mouse button (anywhere, even over a node), the left button on blank canvas space (this is the Mac trackpad「click and drag」path), or Space + left button (even over a node, when the drag has to start on a card). Content follows the pointer 1:1, blank space shows a grab hand, and the canvas no longer chains its scroll to the host page at the edges. A 4px threshold separates drag from click, so clicking blank space still clears the selection and clicking a node still focuses it — while a real drag no longer wipes the selection ring on release. Built on pointer events with pointer capture, so the pointer keeps dragging even outside the panel; touch input is left to native scrolling so momentum survives; and the gesture never re-renders the tree (cursor and text-selection lock go straight to the DOM, because a re-render would re-measure every `TreeRow`). Refinements from review (`_issues/001`): sub-threshold jitter no longer writes `scroll` or flashes the grab cursor (a 1–3px hand tremor on a plain click used to shift the canvas, and because the formula is absolute from the press anchor, skipping those early writes still tracks the pointer 1:1 — the full displacement lands in one jump once the threshold is crossed); the grab cursor and the text-selection lock are now applied on the first crossing rather than on press; a pan whose `setPointerCapture` failed no longer hangs when the pointer is released outside the scroller (a later button-less move ends the pan instead of dragging the canvas with nothing held); the click-suppression flag is cleared at the very top of every gesture rather than after the early returns, so a finished drag can no longer leak into a later tap that takes a non-panning path (touch tap, left-press on a node) and swallow it; and a second pointer can no longer drive an existing pan's anchor.

## [0.5.1] - 2026-08-26

### Added

- Block concept for mindmap nodes (per the 003 living spec): markdown content is now parsed into typed blocks — text, markdown (inline formatting), code (compact summary line + hover overlay for the full source), quote, and table (full grid, cells never split) — while block-level structure syntax (headings/lists/quotes) is split into child nodes and plain paragraphs are promoted to nodes instead of being stuffed into a parent's description. Inline formatting never triggers splitting.
- Unified clickable links: any URL inside any block is rendered complete (never abbreviated), clickable, and opens in a new browser tab; links inside the hover overlay are clickable too.

## [0.5.0] - 2026-08-26

### Added

- Step-by-step update convention for the AI (system prompt + `mindmap_update` tool description): when an edit has several parts, call `mindmap_update` as soon as each part is ready instead of one giant update at the end — each call still carries the FULL document, and the panel's growth animation makes the tree visibly grow while the AI works. The former "panel updates in one step" wording is removed as it pointed the other way.
- Progressive growth animation for the mindmap panel: after every update, newly added/changed nodes fade in one by one (breadth-first from root to leaves, staggered up to 90ms apart) instead of the whole tree popping in at once. Diffing rides on the existing stable structural node IDs, so unchanged nodes never flicker or replay; the total duration is compressed to stay within ~2s for large documents; connector lines fade in together with their new children. The layout is computed in full up front, so the animation is compositor-only (opacity/transform) with no reflow cost. A new `growthAnimation` setting (settings panel「面板」→「生长动画」, default on) turns it off entirely, and `prefers-reduced-motion` is respected. The `mindmap_update` contract is unchanged (still full markdown).
- Theme token system: node colors/corners/shadows/states are produced by the pure function `resolveNodeStyle` from theme tokens, so card style and color theme switch cleanly without touching layout code.
- Node right-click menu gains「复制全文」: copies a node's complete own content to the clipboard (code blocks take the full fenced source, table blocks re-emit a valid Markdown table with the separator row restored, everything else takes the raw text), alongside the existing 复制为图片 / 导出为图片. A divider line separates the node title from the actions.
- Hover overlay for truncated content: prose blocks cut by the line clamp and code blocks share one floating panel (fixed-positioned outside the zoom layer, scrollable full text); it always docks to the right of the node box and never flips left — clicking a node auto-focuses it into view, so hovering again always shows everything.

### Changed

- Length governance for node boxes (020 revision, replacing unbounded full-wrap): every node box is capped at 320px wide so long content wraps instead of stretching into horizontal bars. Prose blocks (text/md/list/quote-topic) that still exceed 6 wrapped lines are clamped with an ellipsis — full text stays reachable via the hover overlay. The clamp uses an integer 20px line-height plus a hard `maxHeight` guard so the cut line never leaks half a glyph. Structural blocks keep their shape: tables get their own wider 680px writing surface (overflow scrolls inside the box) with per-cell min/max width guards.
- GFM table parsing contract: a literal `|` inside a cell must be escaped as `\|` and the parser now honors it; column count is pinned to the separator row — short rows are padded with empty cells and extra cells are dropped, so the grid never turns ragged even when a row contains unescaped pipes.
- Split the two largest client source fragments for maintainability (pure physical split, no behavior change): `src/client/ui/render.js` (572 lines) becomes `slot.js` (MindmapSlot) + `render.js` (NodeBox/TreeRow) + `canvas.js` (the 016 canvas/zoom region), and `src/client/ui/panel.js` (790 lines) becomes `panel.js` (component head) + `treetab.js` (the 013 directory-tree tab region) + `panelbody.js` (layout effect + overlay JSX). The split was done by line slicing only, and the rebuilt `client.js` is byte-for-byte identical to the previous build. `build-client.mjs` now decides fragment separators via an explicit `PHYSICAL_SPLITS` set instead of hard-coded list indices.

### Fixed

- The panel now reliably auto-opens when the AI completes `mindmap_open` / `mindmap_create`. A structural fingerprint of the session nodes (`nodesFingerprint`) feeds a second `useSession` selector; its value comparison bypasses the reference-equality short-circuit that starved the auto-open effect whenever the host store mutated the nodes array in place.
- The "AI 正在打开脑图…" loading state is no longer a dead end. Snapshot documents whose path differs from the tree-click key only by letter case (macOS case-insensitive filesystem) now merge automatically; errored mindmap tool results (`isError` or `ok !== true`) surface as an inline error; a ~30s watchdog switches to a timeout state. Both failure states offer a one-click retry that re-sends the open request.

## [0.4.1] - 2026-08-24

### Changed

- The browser client is now maintained as focused source fragments under `src/client/` and assembled into the single runtime `client.js` entry by `npm run build:client`.
- AI create/open/view intent now restores and switches the target mindmap panel smoothly, including repeated opens and open-then-update tool sequences.
- Added release metadata, a `prepack` verification hook, and an npm package dry-run check to the documented development flow.

### Added

- Centered mindmap canvas with a floating zoom bar at the canvas top-right (zoom out / percent / zoom in / fit): the tree opens centered in the panel (scrollable, no edge clipping when larger), auto fit-to-view on open (no enlargement beyond 100%, 25% floor for giant trees), 25%–300% step zoom with a stable viewport-center anchor, and smart re-fit on AI edits or panel resize until the user zooms manually. Connector lines use `vector-effect: non-scaling-stroke` so they stay crisp at any zoom level, and natural-size debouncing keeps "fit" from oscillating with scrollbar appearance. Clicking any node focuses it: the node scrolls to the left-center of the canvas and the view zooms until the node and its whole subtree are visible (capped at 100%). Zoom math is exposed as pure functions (`clampZoom` / `stepZoom` / `fitZoom` / `focusZoom`) with unit tests.
- Settings page section「思维脑图」in the left nav (`settings.section`), backed by a host settings namespace (`mindmap`): node theme (line style curve/elbow, card corners rounded/square, three color themes ocean/sunset/forest), default panel width (20-80%). The `requireApproval` switch stays functional (read at tool pre-execute time) but is hidden from the UI by design. Introduces a `@deepseek-ai/schemastery` dependency for the settings schema.

## [0.1.0] - 2026-08-23

First release of dsh-mindmap: a plain Markdown file in the session working directory becomes a live mindmap.

### Added

- **Tools**: `mindmap_create` / `mindmap_open` / `mindmap_get` / `mindmap_update` (host side). Root node title = filename, bidirectional sync via `renameRoot` (file rename, collision-safe); path escape protection; `requireApproval` switch (default off) with a `tools/pre-execute` ask hook.
- **Live panel**: consumes the session snapshot (`mindmap_*` tool results) and re-renders on every AI edit — no custom event channel needed.
- **Floating panel (overlay)**: registered in the session header slot with a fixed-position host layer (better-sidebar-style self-bootstrap); toggled by the 思维脑图 button, auto-opens on AI `mindmap_open`/`create`; **layout-push** squeezes `#root` so the chat shifts left instead of being covered; drag-resizable width (280px ~ 80% viewport) persisted in localStorage. The `details` slot is left to the official tool-details panel.
- **Directory tree tab**: persistent tree of the session working directory served by a plugin-owned read-only route (`POST /mindmap/api/tree`, Node fs, same-origin fence, path containment); lazy per-directory loading, dirs-first sorting, hidden entries; right-click to create a mindmap (root or inside a directory); `.md` click opens the tab instantly (placeholder + loading spinner) and auto-sends `mindmap_open` to the AI; the tab renders nodes only after the AI result lands.
- **Single-mindmap mode**: two tabs (目录 / 脑图), opening a new `.md` replaces the current mindmap; close button + right-click menus.
- **Focus sync ("what you see is what the AI edits")**: when the visible mindmap differs from the AI's working document, the panel fills and submits `mindmap_open` automatically (suppressed while the panel is closed).
- **MarkGrove-style rendering**: heading hierarchy (H1 under root, H2 under the preceding H1), nested lists with 2-space indentation, empty list items as placeholder nodes, code blocks as `[lang] first-line` leaf nodes, paragraphs as node notes, stable structural IDs; **orthogonal connector lines** between nodes (measured SVG layer); first H1 that echoes the root title merges into the root node.
- **PNG export**: SVG serialization → canvas → PNG download.
- **Visual system**: emoji folder icons, `.md` "M" badge, hover highlights, active-tab indicator, spaced layout aligned to the tab edge.
- **Project hygiene**: numbered record documents live in `docs/` (gitignored); README (en/zh) and CHANGELOG.
- **Tests**: 42 cases via the Node built-in test runner (`npm run verify`), covering tools, path safety, approval hook, markdown parsing, document replay/merge, tree rows, HTTP route guards, and slot registration.

### Known limitations

- Manual edits to a `.md` outside the AI tools are only picked up on the next AI `mindmap_*` touch of that file.
- The panel is unavailable in a blank (no-session) state, since it lives in a session-scoped slot.
- ~~Running two layout-push plugins (e.g. dsh-better-sidebar) with both panels open at once is a known boundary: both target `#root` and the later-injected rule wins.~~ Resolved in `[0.10.1]`: when Better Sidebar is installed, the mindmap registers as a native tab inside it and no longer writes its own layout-push CSS.
