Commit Graph
683 Commits
Author SHA1 Message Date
Atlas Zeng 35c5c05d6e Merge pull request #603 from omdsh-dev/feat/dsh-0.1.5-alpha-compat
feat(dsh): 适配 DSH 0.1.5-alpha.1(阶段一:基线 + 契约修复)
2026-09-09 23:51:27 +08:00
Menghuan1918 bec90212f5 docs(plans): record the alpha.2 verification evidence 2026-09-09 23:17:13 +08:00
Menghuan1918 0e9b9169eb docs(readme): cite the alpha.2 adaptation PR in the changelog 2026-09-09 23:10:08 +08:00
Menghuan1918 0b6ac5d267 feat(dsh): adapt to DSH 0.1.5-alpha.2
Baseline moves to DSH 0.1.5-alpha.2 only (peer floor ^0.1.5-alpha.2,
CI pin, manifest engines); alpha.1 users stay on v0.19.0-alpha.0.

Host-contract changes handled:
- the native guide entry lost its `description` field (entries are
  icon+title capsules now): `TabDescriptor.description` and the six
  `guideDesc*` keys plus the `nativeGuideDesc` fallback are removed.
- the file address grammar changed: `fileAddressFor` always returns a
  session-scoped address (absolute paths keep their leading `/`),
  `parseFileAddress` is prefix-based and strips `?`/`#`. The plugin's
  purity-gated mirror follows.
- global main panels replaced the `conversation` root slot with the
  keyed `main` slot (`main.conversation`): the bottom workbench's
  center-column locator now accepts both keys and skips
  `display: contents` slot hosts.

Fixes a pre-existing fill bug the alpha.2 probe made obvious: the native
tab body host is a block scroller with a definite height, so tab roots
that only declared `flex: 1` collapsed to content height and the side
chat composer drifted off the pane bottom. The native adapter now wraps
every tab body in a `height: 100%` column flex host.

Also promotes the dependencies `dsh-client-ui-primitives@0.1.5-alpha.2`
dropped from its manifest (anser / shiki / mdast-util-* / micromark-* /
katex / @shikijs/langs) and the newly unmet `dsh-session-persistence`
peer into devDependencies.
2026-09-09 23:09:48 +08:00
Menghuan1918 d4765819c0 feat(guide): let a tab type declare its own line in the new-tab list
The native new-tab list rendered one generic line ("opens in the DSH
sidebar") under every plugin entry, so six plugin rows read identically.
`TabDescriptor.description` now carries the real purpose (string or
locale-following thunk; absent falls back to the generic line), and the
six visible builtins declare theirs across all 20 dictionaries.

- service.ts: `TabDescriptor.description` + docs (guide §4.1, §0 row).
- native/: the guide entry's description follows the descriptor; the
  `files` takeover reuses the editor's line.
- builtins: files / changes / tasks / side chat / terminal / browser.
- tests: builtins assert every visible type declares a distinct line;
  the lifecycle test asserts descriptor lines (and the takeover reuse).
2026-09-09 22:25:59 +08:00
Menghuan1918 9ebaa09f80 docs(agents): record the native tab-type registration ordering trap 2026-09-09 15:18:06 +08:00
Menghuan1918 2d9f90b551 docs: link the phase-3 PR in the v0.19.0-alpha.0 changelog 2026-09-09 15:18:06 +08:00
Menghuan1918 223b2545d9 refactor(shell): retire the plugin's own right panel and free windows
DSH 0.1.5 owns the right column (its native Sidebar hosts every tab type
this plugin registers), so the plugin's own right panel, its width drag /
new-session seed prefs, and the free-window feature are gone. The bottom
workbench is the only plugin-owned surface left, and its expand/collapse
control is registered into DSH's session-header utilities list slot (the
header's corner seat belongs to the native sidebar's own control).

- state: one `bottomSplits` tree; `makeDefaultState()` takes no arguments;
  `toggleBottomPanel` / `openTabInBottomPane` are the only landing
  reducers; `setBottomHeight` and `sanitizeState` share the
  `CONVERSATION_MIN` cap (a persisted tall height no longer snaps on the
  first drag).
- service: every landing in the workbench (creation or focus) expands it;
  `SIDEBAR_FEATURES` drops `'floatWindows'`; the open/close/activate tab
  pools cover the single tree.
- shell: `Sidebar.tsx` renders only the bottom workbench; the toggle lives
  in `sidebar/bottom-toggle.tsx`; layout-push pushes height only (no width,
  no `--dsh-sidebar-width`).
- prefs: `openByDefault` / `defaultWidthPercent` / `changesDiffFloat`
  removed from the schema, defaults, client parser, settings page, and all
  20 dictionaries; the retired keys in stored documents are ignored.
- tests: free-window specs deleted, panel/float assertions dropped,
  remaining suites adapted (124 files / 1287 tests); the mount lane asserts
  the header toggle and sweeps the native sidebar's guide page.
- docs: README / README_EN / guide §0,§11 / AGENTS test map.
2026-09-09 15:18:06 +08:00
Menghuan1918 91de0e2932 fix(native): give the "Files" guide row its glyph
The guide renders `entry.icon`, and the `files` kind takeover registered
without one — so after the editor type stopped contributing its own
duplicate row (b56ceec), the single "Files" row was the only entry in the
new-tab list with a blank icon slot. The takeover now carries the editor
descriptor's icon (the takeover IS that descriptor's page).

Extracted `guideIconOf` so both registration sites build the icon field
the same way; the lifecycle test asserts the takeover's guide entry has
an icon.
2026-09-09 15:18:04 +08:00
Menghuan1918 b56ceec41d fix(native): offer one "Files" row in the new-tab list
The editor type's page identity IS the `files` kind takeover (same
explorer, same title), so the guide/new-tab list showed two identical
"Files" entries — observed on a real profile after the takeover landed.
The editor type now contributes no guide entry of its own; it stays
registered as the file RESOURCE viewer. Covered by the registration
lifecycle test (editor: no guide, files takeover: guide).
2026-09-09 15:06:31 +08:00
Menghuan1918 4da2b43610 fix(native): title a file tab by its own name, not the descriptor's
The editor type serves both the files PAGE and every file RESOURCE; its
native `title` callback ignored the address, so every opened file showed
up in the strip as "Files" — indistinguishable tabs. Verified on a real
profile: after opening `AGENTS.md` from the tree and a chat file link,
the strip read ["文件", "文件", "文件"].

`title(address)` now returns the file name for a file address and keeps
the descriptor title for a page address (`sidebar://editor`). Covered by
the registration-lifecycle test in tests/native-surface.spec.ts.
2026-09-09 14:34:08 +08:00
HuanLinOTO 9a2c56eb42 test: 按 Copilot review 修测试健壮性(localStorage try/catch、动态 uuid);记录 pinned 跨会话限制 2026-09-09 14:29:44 +08:00
HuanLinOTO 9ce3c2706a fix(sidebar): 等待 banner 字体继承 UI 令牌,去掉整句 monospace 覆盖 2026-09-09 14:08:29 +08:00
HuanLinOTO 8c452104e5 docs(plans): 终端等待 banner 实施偏差记录 2026-09-09 13:47:05 +08:00
HuanLinOTO b3ebe52c3e feat(sidebar): agent 终端 tab 标题在等待时显示 ⏳ 徽章 2026-09-09 13:43:29 +08:00
HuanLinOTO c4c856a450 feat(sidebar): agent 终端等待 banner(needle + 跳过等待按钮) 2026-09-09 13:37:08 +08:00
HuanLinOTO 1fa777293f feat(sidebar): 镜像 agent 终端等待状态到 agentWaits(瞬态) 2026-09-09 13:29:27 +08:00
HuanLinOTO 993f7aaacd feat(terminal): terminal_wait_for 增 skipped 结果与 agent-pty.skip-wait 路由 2026-09-09 13:26:36 +08:00
HuanLinOTO fb2afabc72 feat(terminal): registry 支持等待登记、skipWait 与 skipped 结果 2026-09-09 13:20:08 +08:00
HuanLinOTO 2448068369 docs(plans): 修正计划 Task 5 测试(stub 描述符、UI tab 断言、文本计数) 2026-09-09 13:15:49 +08:00
HuanLinOTO d660f33966 docs(plans): 终端等待 banner 实施计划 2026-09-09 13:12:19 +08:00
Menghuan1918 aa1b9945d9 fix(native): register the plugin's tab types when the registry service arrives
The native seat declares `sidebar.right.pane.tab` BEFORE it provides
`sidebarRightTabs`, so driving the registration off the slot declaration
read the service as missing and registered nothing — permanently, since a
declaration never collapses. Observed on a real DSH profile (0.1.5-alpha.1,
`dsh web` on the web profile): the plugin host and its other slot
registrations mounted, the native guide page stayed empty, and a probe
printed `sidebarRightTabs= undefined` at declaration time with the registry
present three seconds later. The scratch mount lane never saw it because
there the plugin activates after the native package finished providing.

`registerNativeSurface` now waits on the SERVICE (`ctx.inject`), which
re-runs whenever the registry appears/reappears, and keeps the per-body
slot registrations on `slots.inject` (they only need the declaration).

Regression test: `tests/native-surface.spec.ts` models the real order
(slot callback fires first, registry arrives later) and fails on the old
implementation. Verified on the real web profile: all guide entries
(files / editor / git / subagent / sidechat / terminal / browser) appear
and the terminal tab renders.
2026-09-09 13:07:53 +08:00
HuanLinOTO 4a6a77a27e docs(plans): 终端等待 banner + 跳过等待设计 2026-09-09 13:03:34 +08:00
Menghuan1918 d7e3e2b986 fix(test): give the native-surface terminal descriptor a component
TabDescriptor requires `component`; the fixture omitted it, so CI's
typecheck step failed on the phase-2 branch (vitest never compiled the
file's props strictly, so the local run stayed green).
2026-09-09 12:33:49 +08:00
Menghuan1918 51546f7433 feat(native): render the plugin's tabs in DSH's native right Sidebar
Phase 2 of the 0.1.5 adaptation: the plugin stops drawing its own right
panel and contributes tab TYPES to DSH's native right Sidebar, which owns
the right column. The bottom workbench (terminal dock) stays plugin-owned.

Registrations (src/client/native/):
- every TabDescriptor becomes a native tab type (kind = descriptor.id, the
  default `extension` band) plus a keyed body in `sidebar.right.pane.tab`
  and a live chip title in `sidebar.right.pane.tab.title`
- the `editor` type also claims `dsh-resource://file/**`, so it outranks
  the built-in `text` preview (fallback) and every file the product opens
  lands in the plugin's editor
- the built-in `files` page kind is TAKEN OVER by an extension
  registration of the same kind: `openTab('files')` draws the plugin's
  explorer, and the built-in resumes when the plugin unregisters
- registrations follow the plugin registry + the side-card settings: a
  type registered later (external plugin) gets its native type too, a
  disabled type is unregistered (absent from the guide, refuses opens)

Adapter (src/client/native/tab-adapter.tsx):
- one synthetic plugin `SidebarTab` per native tab id, minted from the
  native record + navigation params (or the descriptor's own createTab,
  which a native guide open bypasses), kept live across navigations and
  updateTab writes
- the explorer's per-tab expansion/reveal sets and the per-kind instance
  counter, which the plugin's own layout used to hold

Service routing (src/client/service.ts):
- `setSurface` installs the native write face; openTab/openFile land
  natively (a path becomes a resource address, a path-less editor open
  becomes the `files` page, the rest carry the seed as navigation params),
  closeTab/updateTab/activateTab recognise native tab ids
- new optional `OpenTabSeed.target` ('right' default, 'bottom' keeps the
  bottom workbench); the bottom panel's own + menu and auto-terminal pass
  'bottom'
- src/client/native/surface.ts probes the controller's per-session writes
  (`openTabIn`/`openResourceIn`/`closeIn`, not in `ISidebarRight`) and
  queues opens for a session whose surface is not mounted yet

Right panel retired behaviourally (Phase 3 deletes the code): Sidebar
renders its bottom workbench only, keeps the panel collapsed, and hides
the right-panel toggle. The panel-specific e2e specs are removed; the
mount lane now drives the native guide (tab sweep), the plugin explorer
inside the native pane (editor/mermaid/README chunks) and the sidechat
host routes.

Verified: pnpm typecheck / lint / build / test (126 files, 1349 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (7/7).
2026-09-09 11:47:31 +08:00
Menghuan1918 ba0114749f feat(dsh): adapt to DSH 0.1.5-alpha.1
Move the plugin onto the DSH 0.1.5-alpha.1 baseline (peer floor
^0.1.5-alpha.1, CI pinned to the alpha dist-tag) and fix the two host
contract changes that alpha line introduced.

Baseline:
- all @deepseek-ai/dsh-* peer/dev deps -> 0.1.5-alpha.1; optional peer
  @deepseek-ai/dsh-client-ui-sidebar-right (declared, unused until the
  native-surface phase); devDep @deepseek-ai/dsh-agent-loop (inbox fold)
- dsh.plugin.json engines >=0.1.5-alpha.1; version 0.19.0-alpha.0;
  SIDEBAR_SERVICE_VERSION follows

Live assistant stream (assistant/chunk is gone):
- 0.1.5 publishes in-flight model deltas as process-local
  agent/assistant-stream frames instead of durable assistant/chunk events
  and embeds the settled stream in assistant/message / assistant/attempt.
  src/assistant-live.ts folds the frames into a bounded per-session buffer;
  sidechat.events serves the current attempt as `live` rows (replaced every
  poll, never a delta) and the transcript renders them; the inherited
  in-progress snapshot reads message content / attempt stream / live frames.

Cold session reads (persistence.inspect is gone):
- 0.1.5 reads a stored session through an explicit read handle
  (open(id,'read') -> handle.read() -> close()). src/session-store.ts owns
  that protocol; every cold read (sidechat transcript, cold-resume preset,
  session.cwd, changes replay) goes through it.

Dead code and docs:
- drop the remote.session.openWorkspacePath interception: 0.1.5's chat opens
  files through ctx.sidebarRight.openResource instead, so the wrapper had no
  caller and would hijack the host's "open in app" gesture; the
  interceptOpenPath pref, its settings row and its locale keys go with it
- AGENTS.md §3 + external-plugin-guide.md describe the new baseline
- vitest setup bridges Node's localStorage accessor to jsdom's store (the
  pre-existing 26 failures on Node >= 22)
- desktop-layout e2e seeds through host RPC and types into the Lexical
  composer (0.1.5 replaced the textarea) instead of driving the directory
  picker

Verified: pnpm typecheck / lint / build / test (124 files, 1327 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (18/18).
2026-09-09 11:15:30 +08:00
Atlas Zeng d88dcfc3a5 Merge pull request #595 from omdsh-dev/feat/wait-for-regex-needle
feat(terminal): wait_for needle 支持 regex,found 返回实际匹配文本
2026-09-08 23:27:45 +08:00
HuanLinOTO 3e408dbe48 feat(terminal): wait_for needle 支持 regex,found 返回实际匹配文本
needle 以 JavaScript 正则优先(编译失败回退字面匹配,旧字面 needle 完全兼容);found 结果新增 match 字段回报实际匹配文本——单个 needle 可覆盖多结局场景(如 (BUILD_OK|BUILD_FAIL) 同步构建成功/失败,match 区分命中分支);工具 description / needle 参数 / output schema / render 同步更新并写明多重匹配场景;新增 3 个 pty 级测试(alternation 命中分支、非法正则回退、元字符匹配);全量测试失败集合与干净 main 基线完全一致(27 个失败均为预存在 win32 非回归)
2026-09-08 23:07:07 +08:00
Atlas Zeng eecf9dc536 Merge pull request #593 from omdsh-dev/feat/terminal-shell-quoted-args-probe
feat(terminal): 终端 shell 配置加固——引号解析、POSIX 预检、本地化报错
2026-09-08 22:28:22 +08:00
Menghuan1918 ad22abcfe6 docs(plans): 修正实施偏差记录的 commit 引用(cbbed88 → 8704994) 2026-09-08 22:20:15 +08:00
Menghuan1918 81919516d8 fix(terminal): WS close reason 按 UTF-8 字节截断,避免超 123 字节抛错
ws 用 Buffer.byteLength 校验 close reason 的 123 字节上限(ws/lib/sender.js),
原 slice(0, 100) 按 UTF-16 code unit 截断,非 ASCII shell 名可达 316 字节
(实测 200 个 CJK 字符),ws.close() 会抛 RangeError 顶掉原始错误。
改为按字节截断且不切碎 code point,并补回归测试。
2026-09-08 22:20:15 +08:00
Menghuan1918 162c16ec32 feat(file-icon-theme): 可选彩色图标主题接线(懒加载 chunk + 设置开关 + i18n)
- 新 chunk `file-icons`(`lib/client-file-icons.js`,255 kB / gzip 90 kB):把数据表
  注册成 `registerFileIcon` 描述符——按 glyph+color 归并,563 条规则 → 约 250 条注册;
  目录规则只走 `folderNames`(只认领列出的目录,未列出的仍用内置 glyph)。
- `src/client/file-icon-theme.ts`:pref → loadChunk → 注册/注销,generation 守卫
  丢弃「切换中途落地」的加载;chunk 拉取失败只 log,保持内置 glyph。
- pref `fileIconTheme`(默认 `'builtin'`,未知值按 builtin 处理)+ 设置页
  「文件 → 文件图标」下拉(内置单色 / 彩色品牌图标)。
- chunk 名同步四处(chunk-loader / bundle-route / tsdown / manifest-consistency)+
  `bundle-route.spec`;tsdown 为该 chunk 加 react-icons ESM alias——否则整包打进
  6.4 MB,alias 后 255 kB。
- i18n:6 个新 key 镜像到全部 19 份词典(zh/en 之外),locale 键集守护通过。
- 皮肤契约:指南 §12 增豁免条款(品牌色是内容非 chrome,仅限该懒加载 chunk),
  AGENTS §5 同步;`tests/theme.spec.ts` 新增边界用例(核心图标模块零颜色字面量、
  核心不静态 import 该 chunk)。
- 测试:`tests/file-icon-theme.spec.tsx` 7 例(loader 六态 + 真实数据三表注册);
  prefs 新增默认值/解析用例;builtins / prefs / plugin-shape / smoke 夹具补字段。
- 全量 `pnpm test` 失败集与 main 基线逐条一致(5 文件 / 26 例预存环境失败),
  新增用例全绿;typecheck / lint / build / check:consumer-types 全通过。
2026-09-08 21:43:28 +08:00
fenter 8909455df2 feat(file-icon-theme): 彩色文件图标数据表(源自 #429 by @fenter)
本仓库可选图标主题的数据层:扩展名 / 精确文件名 / 目录名 → 品牌或通用
glyph + 展示色,共 563 条规则(218 扩展名 + 197 文件名 + 148 目录名)。
数据来自 PR #429(fenters/DSH-better-sidebar,feat/file-type-icons),
此处作为本插件内置可选主题复用,作者身份保留。

- 目录表的 Android/android、IOS/ios 两组大小写变体折叠为一条规则(解析器
  按大小写不敏感匹配,重复键会静默互相覆盖)。
- 剔除 44 个未被任何规则引用的 react-icons 导入(#429 写作时本仓库尚未
  接入 ESLint,未触发 unused 检查)。
- 本提交只放数据 + IconEntry/colored;注册与接线在后续提交完成。
2026-09-08 21:25:41 +08:00
Menghuan1918 a9a635138a feat(file-icons): FileIconDescriptor 支持 names / folderNames 与 open 参数
图标主题(VSCode 的 fileNames / folderNames 半边)需要按**文件名**和**目录名**
着色,扩展名规则表达不了 `package.json` / `Dockerfile` / `node_modules`。

- `names?: readonly string[]`:精确 basename(大小写不敏感),命中优先于扩展名。
- `folderNames?: readonly string[]`:精确目录 basename,命中优先于保留扩展名;
  只认领列出的目录,未列出的目录仍走 `'folder'`/`'folder-open'` 或内置 glyph。
- `exts` 变为可选:省略 = 没有扩展名规则(`names`-only 注册不再是 catch-all);
  `exts: []` 仍是全局默认 catch-all,语义不变。
- `icon(path, size, open?)`:目录行的展开态传入工厂,一条注册即可渲染开/合两态。
- `matchFileIcon` 先 names 后 exts;`matchFolderIcon(open, name?)` 先 folderNames
  后保留扩展名(新参数可选,旧调用不变)。
- 指南 §7 同步:字段、匹配顺序、四例注册(names / folderNames / 全目录 / catch-all)。

测试:`tests/file-icons.spec.tsx` 19 → 23(names 大小写、name 优先于 ext、
folderNames 只认领列出目录、open 传入工厂);`tests/consumer-types.ts` 补新字段。
2026-09-08 21:24:03 +08:00
HuanLinOTO e847771954 docs(plans): 回填终端 shell 配置加固实施偏差 2026-09-08 21:21:31 +08:00
HuanLinOTO e8761ee851 feat(terminal): 配置 shell 未找到时显示本地化可操作横幅(全语言) 2026-09-08 21:21:30 +08:00
HuanLinOTO 8aa9441627 feat(terminal): shell 覆盖去引号进解析、标题跟随覆盖、close reason 机器标记 2026-09-08 21:21:30 +08:00
HuanLinOTO 870499430b docs(terminal): resolveShellExecutable JSDoc 跟随 shell-not-found 新错误码 2026-09-08 21:21:30 +08:00
HuanLinOTO 26706bc3e9 feat(terminal): POSIX 也预检 shell 可执行文件,统一 shell-not-found 错误码 2026-09-08 21:21:30 +08:00
HuanLinOTO 829ad49ddf feat(terminal): 引号感知的 shell 参数解析与路径去引号纯函数 2026-09-08 21:21:30 +08:00
HuanLinOTO de0eac54b7 docs(plans): 终端 shell 配置加固设计与实施计划 2026-09-08 21:21:29 +08:00
Menghuan1918 8722bfb29c fix(file-icons): 适配 v0.18.1 基线——重命名行接入解析器 + 测试与门禁补齐
rebase 到 main(v0.18.1)后的适配层:

- 冲突(EditorHost ×2 / TreePanel / FileTree)按并集解:main 侧
  onPathRenamed/onPathDeleted 与 PR 侧 service prop 并存。
- main 新增的行内重命名行仍用 <VscFile>/<VscFolder>,而本次改动已删除
  VscFile import(合并后 TS2304)——改走 dirRowIcon/fileRowIcon,与树行
  同一条回退链;VscFolder 随之无引用,从 import 移除。
- tests/file-icons.spec.tsx:markerB 换成独立元素类型(<b>),使
  「priority 降序 / 同级按注册序」断言真正可区分(此前两个 marker 都是
  <span>,断言恒真);新增「工厂返回 undefined 则拒绝该级、链继续」与
  「注册/注销触发订阅通知」两例(17 → 19)。
- tests/consumer-types.ts:补齐 FileIconDescriptor / registerFileIcon /
  getFileIcons / matchFileIcon / matchFolderIcon / fileIcon / folderIcon /
  FOLDER_EXT / FOLDER_OPEN_EXT 的编译期门禁。
- README:v0.19.0(未发布)段移到「最近更新」最前(此前被自动合并夹在
  v0.18.1 与 v0.18.0 之间)。
- file-icons.tsx:注释修正——展平后的 Map 是后写覆盖,不是「first match
  wins」;当前无重复扩展名键,仅潜在语义。
2026-09-08 21:14:15 +08:00
Menghuan1918 8025b83a45 feat(file-icons): 目录保留扩展名注册 + 全局默认兜底 + 编辑器文件 tab 对应
- registerFileIcon 保留 'folder'/'folder-open' 特殊扩展名:目录行(含根行)
  图标可注册,匹配保留值不认领真实 .folder 文件
- 新增权威解析器 fileIcon(path,size)(具体注册 → 内置 glyph → catch-all
  全局默认 → VscFile,逐工厂崩溃隔离)与 folderIcon(path,open,size);
  matchFileIcon 收窄为仅具体扩展名匹配
- 编辑器文件 tab(非 meta.dir)与文件树行显示同一 fileIcon 图标
- 同步 external-plugin-guide §7(保留值/回退链/三例注册)与 README
  (功能一览、v0.19.0-alpha.0 发布注记、服务化扩展章节)
- tests/file-icons.spec.tsx 重写为 17 用例(链序/保留值/目录/崩溃回退)
2026-09-08 21:11:32 +08:00
Menghuan1918 05773e0f2a feat(file-icons): 文件树按扩展名区分图标 + 对外 registerFileIcon API
- 新增内置扩展名→glyph 映射(md/媒体/pdf/json/代码/配置/数据库/压缩包,
  单色 currentColor 遵循皮肤契约,未知扩展回退 VscFile)
- ctx.betterSidebar 新增 registerFileIcon/getFileIcons/matchFileIcon
  (priority 降序,同优先级 catch-all 垫底),features 增加 'fileIcons'
- FileTree 文件行消费注册表:注册图标优先(可为彩色 ReactNode,
  工厂抛错回退内置),注册/注销即时生效
- 同步 docs/external-plugin-guide.md §7 与 tests/file-icons.spec.ts
2026-09-08 21:11:32 +08:00
Atlas Zeng 146840bb4f Merge pull request #586 from omdsh-dev/chore/release-0.18.1
chore(release): 0.18.1
v0.18.1
2026-09-08 16:20:03 +08:00
Menghuan1918 364a48df98 chore(release): 0.18.1
- package.json / dsh.plugin.json / SIDEBAR_SERVICE_VERSION → 0.18.1(三者锁步由
  tests/service.spec.ts 与 tests/manifest-consistency.spec.ts 断言)
- README / README_EN:新增 v0.18.1 条目并同步三处徽章版本号——自 v0.18.0 起的
  11 个 PR:#499 变更面板预览增强(md 阅读模式 / html·pdf 渲染 / 密钥脱敏)、
  #550 文件树重命名与删除、#535 目录名词典化、#562 侧边对话种子补 fork 标记、
  #569 markdown 本地图片改写、#576 git 折叠上下文按需展开(含 git.show 路由
  rev:path 修正),以及 #524/#526/#527/#534/#536/#540/#541/#542 的内部收敛
- docs/external-plugin-guide.md:当前版本行 → v0.18.1
- DSH 基线不变(peer ^0.1.2-rc.1,CI 钉 0.1.2-rc.1),无运行时行为改动

验证:make check(typecheck → lint → build → test → check:consumer-types)全绿,
124 test files / 1319 passed, 9 skipped
2026-09-08 16:16:16 +08:00
Atlas Zeng df1f3b9666 Merge pull request #576 from LaoYueHanNi/fix/diff-fold-expand
fix(diff): git 折叠上下文按需展开,修复「点击展开」无反应
2026-09-08 16:14:53 +08:00
haruhi 5bf7dfe3c4 fix(git): git.show 路由改用仓库相对路径,修复折叠展开空白
- src/index.ts: git.show 的 path 不再经 resolveGitPath 转绝对路径——rev:path 语法只认仓库相对路径(diff 输出剥 a// b/ 前缀的形态),绝对路径恒 fatal 返 null,折叠展开渲染空行;透传后暴露面与 git.diff/git.log 一致(仅本仓库版本树)
- src/client/changes/DiffPane.tsx / src/client/DiffTab.tsx: 两侧内容皆空时抛错走「上下文未加载」降级,不再静默空白展开

背景:真机 link 测试发现展开后 marker 消失但零行渲染——git.show 路由此前无真实调用方,resolveGitPath 的工作区语义与 rev:path 树寻址不匹配的缝隙首次暴露。
设计 docs/plans/2026-09-07-git-fold-expand-design.md 实施偏差第 8 条同提交。
2026-09-08 00:04:25 +08:00
haruhi 900654b8fd fix(diff): git lens 折叠行接入按需展开,修复点击无反应
- src/client/changes/DiffPane.tsx / src/client/DiffTab.tsx: 按 SidebarDiffRef 构造 fold loader——commit 侧 <hash>^ 与 <hash>;staged 侧 HEAD 与 :0;unstaged 侧 :0 与工作区 fsRead(失败/二进制降级空串);per 文件内容 Promise 缓存,目标或刷新 tick 变化时重置
- fallback 换侧时记录 effectiveStaged,loader 依实际渲染侧取数,修正切片行号错位
- src/client/locales.ts 及 19 个 locales-*.ts: 新增 changesFoldLoading / changesFoldUnavailable 全量补译(共 20 文件 21 处)
- tests/diff-fold-expand.spec.tsx: 展开/折叠/缓存去重、失败降级、无 loader 保持 inert、session 路径回归

背景:git 提交/工作区 diff 折叠行承诺「点击展开」却永远不可点,与 session lens 行为不一致;接入状态机后承诺为真,session 路径与现有文案零变化。
设计 docs/plans/2026-09-07-git-fold-expand-design.md §4.5-4.6。
2026-09-07 23:51:10 +08:00
haruhi d990c10e59 feat(diff): 折叠行异步展开状态机与内容切片
- src/client/api.ts: 仿 gitCommitDiff 新增 gitShow 封装,走既有 git.show 服务路由(payload {rev, path},返回 content 或 null),服务端零改动
- src/client/diff/rows.ts: 新增 foldRowsFromContents 纯函数——old 侧按 fold 行号切片为 context 行(new 侧经区间偏移映射,落界才带行号),new 侧补纯增行;越界截断、\r 保留
- src/client/diff/DiffRows.tsx: 新增 resolveFold prop;foldData/foldLoading/foldFailed 三态,加载中不可点,失败降级静态标记;foldEpoch 使 segments 重置后 in-flight 回调失效
- src/client/diff/DiffFiles.tsx: resolveFold 下传各文件 DiffRows,fold 级 Promise 缓存去重并发点击与重挂载
- tests/diff-rows.spec.ts: foldRowsFromContents 六用例(对称/纯删/纯增/\r\n/空内容/越界)

背景:git -U3 不输出 hunk 间未改动行,gap fold 只有行号范围没有 rows,渲染复用「点击展开」文案却无交互;展开能力建为独立状态机(含 api 封装),git lens 只需注入取数 loader。
设计 docs/plans/2026-09-07-git-fold-expand-design.md §4.1-4.4。
2026-09-07 23:51:06 +08:00