# Changelog All notable changes to this project are documented here. Release-specific notes are also published on GitHub Releases. ## [Unreleased] ### Added - 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_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 - 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.