Commit Graph
241 Commits
Author SHA1 Message Date
ea70dc97eb Recommend dsh-turn-review in the add-tab-plugin catalog (#102)
* Recommend dsh-turn-review in the add-tab-plugin catalog.

* feat: curate dsh-git-forge and dsh-ssh-tunnel in recommended tab plugins

Add both community tab plugins to the Side card "add tab plugin" catalog
(plugins-tabs.ts + zh/en descriptions), keep alphabetical id order, and
note them under v0.13.0 recent updates.

* ✨ 推荐插件目录新增 dsh-docs-panel(全局文档)

- plugins-tabs.ts:追加 dsh-docs-panel Tab 条目(npm 包名 id、GitHub 仓库、
  安装命令先装 dsh-better-sidebar 再装本插件,与 dsh-sidebar-qa 同款硬依赖写法)
- locales.ts:新增 pluginDocsPanelDesc 的 zh/en 翻译

* docs: recommend dsh-flowglass sidebar tab

---------

Co-authored-by: thirsty5034 <thirsty5034@users.noreply.github.com>
Co-authored-by: mlosun <mlosun@qq.com>
Co-authored-by: Iwctwbh <iwctwbh@gmail.com>
Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-21 22:09:29 +08:00
b8e0840f3f feat: curate dsh-git-forge and dsh-ssh-tunnel in recommended tab plugins (#204)
* feat: curate dsh-git-forge and dsh-ssh-tunnel in recommended tab plugins

Add both community tab plugins to the Side card "add tab plugin" catalog
(plugins-tabs.ts + zh/en descriptions), keep alphabetical id order, and
note them under v0.13.0 recent updates.

* ✨ 推荐插件目录新增 dsh-docs-panel(全局文档)

- plugins-tabs.ts:追加 dsh-docs-panel Tab 条目(npm 包名 id、GitHub 仓库、
  安装命令先装 dsh-better-sidebar 再装本插件,与 dsh-sidebar-qa 同款硬依赖写法)
- locales.ts:新增 pluginDocsPanelDesc 的 zh/en 翻译

* docs: recommend dsh-flowglass sidebar tab

---------

Co-authored-by: thirsty5034 <thirsty5034@users.noreply.github.com>
Co-authored-by: mlosun <mlosun@qq.com>
Co-authored-by: Iwctwbh <iwctwbh@gmail.com>
Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-21 22:09:06 +08:00
3004604a2d docs: add dsh-flowglass to recommended tab plugins (#261)
* ✨ 推荐插件目录新增 dsh-docs-panel(全局文档)

- plugins-tabs.ts:追加 dsh-docs-panel Tab 条目(npm 包名 id、GitHub 仓库、
  安装命令先装 dsh-better-sidebar 再装本插件,与 dsh-sidebar-qa 同款硬依赖写法)
- locales.ts:新增 pluginDocsPanelDesc 的 zh/en 翻译

* docs: recommend dsh-flowglass sidebar tab

---------

Co-authored-by: mlosun <mlosun@qq.com>
Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-21 22:08:06 +08:00
mlosunandMenghuan1918 2d2b42722a ✨ 推荐插件目录新增 dsh-docs-panel(全局文档) (#230)
- plugins-tabs.ts:追加 dsh-docs-panel Tab 条目(npm 包名 id、GitHub 仓库、
  安装命令先装 dsh-better-sidebar 再装本插件,与 dsh-sidebar-qa 同款硬依赖写法)
- locales.ts:新增 pluginDocsPanelDesc 的 zh/en 翻译

Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-21 22:03:42 +08:00
Atlas Zeng 0fdf63b081 feat(settings): 侧边卡片设置页 UI/UX 现代化——卡片底部设置条替代隐形齿轮 (#300)
* feat(settings): 侧边卡片设置页 UI/UX 现代化——卡片底部设置条替代隐形齿轮,协调双色启用态

- 二级设置入口:卡片右下 16px 幽灵齿轮(tertiary 色、像素钉位)改为
  卡片底缘全宽设置条(hairline 分隔 + 齿轮图标 + 「功能设置」文字
  标签),常态可发现;hover 换品牌淡底 + brand 字色引导。可见性条件
  与 aria-label 契约不变(仅父级启用且声明 settings)
- 常规行齿轮升级为带 border-l2 边框的 22px 芯片按钮,hover 同样
  accent 引导
- 启用卡片:品牌描边柔化为 color-mix(45%),勾选徽标改用
  state-success-primary(绿=已开启确认,brand=激活强调的协调双色)
- 节奏:section gap 16 / group padding 20 / 网格 minmax(180px) gap 12;
  计数徽章改为 accent-soft 淡底 pill;开关关态 thumb 降档 +
  track 底 layer-2
- 动效纪律:全部过渡 ≤160ms 且仅颜色属性;prefers-reduced-motion
  扩展覆盖设置条/行齿轮/弹窗行
- 清理:合并 .pluginEntries 重复定义;文档同步(AGENTS.md §5 +
  设计文档新增 §9 偏差记录)

* feat(settings): 按反馈迭代——卡片开关滑块、二级弹窗长列表滚动、卡片等高

- 勾选徽标 → 紧凑品牌开关滑块(30×16 track + 10px thumb,开=品牌填充
  反色拇指,关=中性且不渲染;纯视觉指示,卡片本身仍是 aria-pressed
  开关,语义契约不变)
- 二级弹窗长列表:popupRows 内部滚动 max-height min(52vh,440px) +
  细滚动条(--dsw-alias-scrollbar-* 令牌),终端 6 行场景弹窗壳体与
  Done 固定、仅行区滚动;行密度收紧(padding 12/14、gap 8)
- 卡片等高:.card min-height 106px + .cardMain flex:1 + .cardTop
  min-height 28px——带设置条/不带设置条/无图标卡片全部等高
- prefers-reduced-motion 覆盖 cardSwitch 过渡;文档同步(AGENTS.md
  §5 + 设计文档 §9 迭代记录)

* fix(settings): 二级设置弹窗行区取消横向滚动——box-sizing:border-box + overflow-x:hidden

popupRows 的 width:100% 配 content-box 会在 padding-right:4px 时把
内容盒撑宽 4px,overflow-y:auto 隐式启用的 overflow-x 因此出现横向
滚动条;改为 border-box 并显式 overflow-x:hidden(minifier 输出
overflow:hidden auto),行区始终填满弹窗宽度、无左右滑动。add-plugin
弹窗的 pluginEntries 同步加 overflow-x:hidden。
2026-08-21 21:39:59 +08:00
NuperAkiandMenghuan1918 6cea4bdd6f fix(subagent): 子代理页实时预览改为批量接口,避免 O(N²) history 请求风暴 (#298)
* fix(subagent): 子代理页实时预览改为批量接口,避免 O(N²) history 请求风暴

## 背景

DSH Web UI 中,当主代理同时运行多个子代理时,`dsh-better-sidebar` 的「任务管理/子代理」页面会出现明显卡顿。经调查,卡顿根因不在页面渲染,而在实时预览的请求模式。

## 根因

旧实现中,每个 running 子代理卡片都独立轮询 `subagents.history`:

- N 个 running 子代理 → 每 3 秒产生 N 次 `subagents.history` 请求;
- 每次 `subagents.history` 都会在 host 侧触发一次完整的 `listChildren` 全量子代理目录枚举;
- 子代理数量越多,服务端重复扫描越严重,近似 O(N²) 放大;
- 请求超过 3 秒时,客户端还会 abort 旧请求并重发,进一步形成请求风暴。

## 改动内容

1. **新增 host 批量接口 `POST /sidebar/api/subagents.live`**
   - 请求 `{ rootSessionId }`;
   - 通过 `ctx.subagents.listDescendants(rootSessionId)` 一次枚举整棵子代理树;
   - 仅对 running 且非 Side Chat 的子代理,从 session events 尾部提取最新 `text/tool` 活动;
   - 返回 `{ live: Record<childId, { text?, tool? }> }`;
   - 缺 `subagents`/`agents` 服务或枚举失败时返回 503;单个子代理日志异常只跳过该子代理。

2. **客户端改为单轮询 + 单飞行请求**
   - 删除每个 running 子代理独立的 `subagents.history` 定时器;
   - 在 `SubagentView` 中合并为一个 `useSubagentLive` 轮询 hook;
   - 使用递归 `setTimeout` 和单在途请求,慢请求不再被 abort/重发;
   - 页面隐藏或 root 变化时停止轮询并中止在途请求。

3. **解析器优化**
   - `lastActivity` 改为接收原始 `SidebarSessionEvent[]`;
   - 从事件尾部向前扫描,找到最新 text 和 tool 后提前停止,避免复制/遍历完整历史。

4. **UI 保持不变**
   - 实时预览的展示逻辑、文案、截断规则均未改变。

## 测试

- 新增 `tests/subagent-live-route.spec.ts`:覆盖批量接口成功聚合、只返回 running、跳过 Side Chat、缺服务 503、枚举失败 503、`ctx.get` 回退、bad-request。
- 新增 `tests/subagent-live-polling.spec.tsx`:覆盖每轮只发 1 次 `subagents.live`、不再调用 `subagents.history`、单在途不并发。
- 更新 `tests/subagent-activity.spec.ts` 与 `tests/subagent-jobs-view.spec.tsx`。
- `pnpm typecheck` ✅
- `pnpm build` ✅
- 相关测试 29 个全部通过 ✅

> 本机完整 `pnpm test` 仍有与本改动无关的环境性失败(node-pty 超时、Windows CRLF 断言、pty-deps 路径断言),不影响本 PR 逻辑。

* fix(subagent): live 预览以 catalog activity 为准,删除 agents 第二事实源 (#298)

- host 路由 running 门改回 entry.activity === 'running'(与客户端卡片渲染门一致),
  恢复 PR 承诺的 UI 不变:turn 间 idle 的 continuable 子代理仍显示最后活动
- 删除 ctx.agents 依赖与 SidebarAgent.status 新增(agents 缺失时不再静默 200 空 map;
  503 契约收窄为只涉及 subagents,与文档/测试一致)
- service 解析统一为 ctx.get('subagents')(兄弟路由惯例;插件未 inject subagents,
  直接属性访问有抛错风险)
- buildSubagentLiveApi 签名改收完整 Context,删除 SidebarSubagentLiveContext 窄接口
- api.ts 复用 LastActivity,删除重复类型 SubagentLiveEntry
- 测试:route spec 删除 agents 相关用例;polling spec 新增页面隐藏停止轮询、
  root 变化清空 live 并重启轮询两个用例

* fix(subagent): live 预览恢复最近 12 条消息窗口,排除陈旧活动 (#298)

CR 反馈:新批量接口把完整 session event log 喂给 lastActivity,
- 长时间只输出文本、早期调用过工具的 running 子代理会持续显示陈旧 tool
- 无 tool 的长会话每次轮询反向扫描整个事件数组,重新引入与历史长度
  相关的 CPU 成本

修复(与旧 subagents.history({ maxMessages: 12 }) 窗口语义一致):
- lastActivity 增加可选 maxMessages 参数:反向扫描按表面消息
  (user/message + assistant/message)计数,窗口满后更老事件一律跳过
- 路由传 LIVE_WINDOW_MESSAGES = 12(导出常量)
- 回归测试:窗口外旧 tool 不返回;窗口内 tool 保留;非消息事件不消耗配额

---------

Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-21 18:15:49 +08:00
Atlas Zeng a9d3451415 feat(adapt): 适配 DSH 0.1.1-rc.1(@next)——devDeps/lockfile/CI 基线平移,peer 下限保持 rc.8 (#297)
调研结论:0.1.1-rc.1 为常规 rc 递进(依赖图零增删、纯版本平移),
所有 sidebar 消费的公开契约(DOM 槽位 / CSS 令牌 / boot 协议 /
MarkdownText codeLabels / webServer 路由 / agents-session API)
逐包比对无破坏,无需代码逻辑改动。

- package.json: devDependencies 14 个升至 0.1.1-rc.1;peer 下限保持
  ^0.1.0-rc.8(rc.8 与 0.1.1-rc.1 双向兼容,rc.8 用户无需升级 DSH);
  version 0.14.1 -> 0.14.2(cordis ^4.0.0-rc.8 不变)
- dsh.plugin.json / src/client/service.ts: 版本锁步同步
- pnpm-lock.yaml: 全量重解析,零 rc.8 残留(干净解析验证)
- CI 挂载冒烟钉版 @deepseek-ai/dsh@0.1.1-rc.1(验证基线)
- README(EN) 横幅/changelog/安装注释、ISSUE_TEMPLATE、AGENTS.md 同步
2026-08-21 18:15:25 +08:00
Atlas Zeng 7f4f84c4cf feat(sidechat): 侧边对话——每对话一 Tab(Codex 对齐)+ 完整上下文继承 + 保存为新会话 (#286)
三轮实测驱动迭代:种子信封透传(surfaceOp 校验)、每线程一 Tab 交互重做完、四连修(fork 丢 this / 自绘 history+save 图标 / collectOwnEvents 大页回源修重开丢工具行 / descriptor 入种子修目录 corrupt 行 + 前缀过滤保拓扑零噪音)。739 单测 + 挂载冒烟 12/12 全绿。
2026-08-21 15:41:14 +08:00
Lin bc6f1e129d fix(markdown): preview local/relative images in .md files
The shared MarkdownText only renders absolute http(s) images (relative
links are disabled for chat security), so a local image referenced by a
previewed markdown file - ![alt](./img.png), an absolute /cwd/img.png,
or a reference definition - showed only its alt text.

Rewrite such destinations into absolute /sidebar/file media URLs (with
the GUI origin prefix) so MarkdownText accepts them and the host media
route serves the bytes (still restricted to the session cwd). The
rewriter is a dependency-free pure helper (markdown-images.ts) with a
focused unit spec; TextEditor passes the rewritten preview text to both
the plain MarkdownText and the mermaid-lazy preview.
2026-08-21 13:40:41 +08:00
Atlas Zeng 50a888845f feat(sidebar): 桌面兼容三方案——自动检测(WCO 标准几何)/壳预设/自定义 CSS (#284)
* feat(sidebar): 桌面兼容三方案——自动检测(WCO 标准几何)/壳预设/自定义 CSS

核心只保留 Web 标准机制,壳专属适配全部下沉到用户空间(KISS):
- prefs 新增 titleBarScheme(默认 auto)/titleBarPresetId/customCss,旧 titleBarCompat
  布尔自动迁移(true→custom 保留 stripPx,false/缺失→auto)
- wco.ts:navigator.windowControlsOverlay 反应式几何(geometrychange 实时更新;
  visible=false 的幽灵 API 视为缺失),实现 #257
- titlebar-strip.ts 唯一 strip 取值链:WCO → dsh-desktop-titlebar-inset 契约参数
  → 预设 stripFor → 手动 px → 0;desktop-env.ts 移除 win32 32px 硬编码
- shell-presets.ts 数据驱动预设(准入:issue/PR 提及 + 100+ star;v1 仅
  anywhere-labs DSH Desktop:darwin 20px / win32 无 WCO 时 32px),opt-in 手动
- 预设/自定义 CSS 注入(data-dsh-preset-css / data-dsh-custom-css 标签),
  chrome 补稳定寻址面 data-dsh-toggle-cluster / data-dsh-panel
- 交互 chrome 统一 -webkit-app-region: no-drag(吸收 #111/#153)
- layout.css 底栏推挤复合锚点([data-pane] 与 :has(> [data-slot]) 双保险,
  rc.8 实测前者失效、后者命中,吸收 #226 思路)
- 设置 UI:位置兼容模式齿轮弹窗改三方案(自动检测/壳兼容方案/自定义方案)
- 测试:wco/titlebar-strip/shell-presets 单测 + prefs 迁移 + e2e 5 场景
  (保守 auto/WCO 反应式/预设/自定义 CSS/复合锚点),挂载门禁 12/12 全绿

核心 grep 无壳专属类名;为某壳做的兼容在另一壳会再坏——预设与自定义 CSS
是唯一出路(AGENTS.md §8 契约已更新)。

* refine(sidebar): 位置兼容模式改主行下拉四选项——自动检测/DSH官方Web/壳预设/自定义

按用户反馈定稿 UI:
- 主行下拉:自动检测(默认)/ DSH官方Web(显式零适配,连 WCO 也强制 0)/
  各壳预设(命中环境带「已检测」后缀,仅提示)/ 自定义方案(保留齿轮弹窗:
  下移距离 + 自定义 CSS)
- 旧文档迁移:titleBarCompat=true 或 titleBarStripPx 非默认值(此前已有配置)
  → 默认落到 custom 方案保留用户数值;干净文档 → auto
- prefs 新增 'web' 方案;strip 取值链最优先处理 web→0
- 测试:prefs 迁移(stripPx 48→custom / 40→auto)、web 强制 0、下拉渲染断言
- 单测 693 通过,挂载 e2e 12/12 全绿

* refine(sidebar): 方案下拉复用 SelectMenu 组件,不用原生 <select>

- 抽出可复用 SelectMenu(primitives Menu:锚点按钮 + 下拉选项卡,支持
  icon/desc/多选),SelectRow 改为其薄壳
- 位置兼容模式方案下拉改用 SelectMenu;选项带描述(自动检测/DSH官方Web/
  各壳预设(命中带「已检测」后缀)/自定义方案)
- 移除原生 select 样式;单测 693 通过,挂载 e2e 12/12 全绿
2026-08-21 01:28:17 +08:00
dsh-better-sidebar-fix 04374b23e4 fix(git): keep worktree views transactionally consistent 2026-08-21 00:45:24 +08:00
dsh-better-sidebar-fix 6d6d582996 fix(git): show changes across linked worktrees
The Git panel only ran git status against the session's own worktree,
so when DSH (or any coding agent) created a linked worktree via
'git worktree add' and modified files there, the sidebar's Git panel
stayed empty - no changed files, no diff, nothing to commit.

Enumerate all linked checkouts of the session repository and let the
user select which one the panel targets. Auto-select the dirty linked
worktree when the primary checkout is clean and exactly one linked
worktree has changes (the common DSH agent pattern). Poll status every
2s while the panel is visible so model-authored edits appear without
a manual refresh.

Security: resolveWorktree validates the client-supplied worktree path
against 'git worktree list' before using it as a git -C cwd. Uses -z
porcelain framing, which disables core.quotePath C-style quoting so
non-ASCII checkout paths round-trip losslessly.
2026-08-21 00:45:24 +08:00
Atlas Zeng 4e52923970 refine(explorer): redraw the drop overlay illustrations (#281)
The first pass read poorly: the upload art was an arrow descending into a
solid slab (a bathtub, not a tray) and the chat art's back card was a bare
cyan blob. The upload zone now shows the classic notched tray with an
arrow rising out of it — the same glyph family as the toolbar's upload
icon — with all four corners rounded; the chat zone's two tilted photo
cards each carry their own sun-over-mountains motif, tilts pivot around
the card centers, and the front card gets a bolder mountain range. Both
were iterated against rendered screenshots (light + dark mask) rather than
by eyeballing path math.
2026-08-20 22:51:31 +08:00
Ch3n4y ffcd6953eb fix(layout): 面板宿主裁剪视口边缘,收起的面板不再撑出文档双向滚动 (#277)
收起的面板以 transform: translate(102%) 滑出屏幕,但 transform 后的元素仍计入祖先滚动溢出区,导致整个页面可左右/上下拖拽。面板宿主 [data-dsh-panel-host] 是视口尺寸的 fixed 容器,补上 overflow: hidden 后裁剪点恰在视口边缘,开合滑入动画、内部滚动容器与 fixed 弹层均不受影响。e2e 增加回归断言:面板收起时文档 scrollWidth/Height 不得超出视口。
2026-08-20 21:11:34 +08:00
bb2456bea9 feat(explorer): upload files into the workspace from the file tree (#239)
* feat(explorer): upload files into the workspace from the file tree

- TreePanel header: upload-files / upload-folder buttons (folder picker keeps
  the tree via webkitdirectory), with a one-line progress/result hint
- FileTree: drop files/folders onto the tree body (workspace root) or onto a
  directory row (that directory); directory context menu gains 上传到此处
- api.ts: uploadFile posts base64 to /sidebar/upload (host writes it through
  fs + shell decode; route provided for now by the local demo plugin)
- upload.ts: bytes->base64 (global-btoa-independent), FileList/DataTransfer
  collection, sequential upload with per-file results; ~48MB per-file cap
- locales: zh/en strings; icons: IconUploadOutline16

* fix(explorer): reload tree after upload and auto-dismiss the status hint

The drop/context-menu upload path cleared the level cache but never
refetched (the refreshTick bump belongs to the button path), so the
visible tree emptied and only the persistent '已上传 N 个文件' hint
remained — the tree only came back after reopening the tab.

- FileTree.uploadInto now reloads root + expanded levels after clearing,
  matching the refresh-tick path
- success status auto-dismisses after 3.5s (failures persist until the
  next action) in both TreePanel and FileTree

* feat(host): add the /sidebar/upload route (raw bytes, workspace-safe)

The file tree's upload UI needs a host route; until now it pointed at a
locally-injected demo route. This is the real implementation:

- src/index.ts: register POST /sidebar/upload (exact route, same fence and
  session-cwd resolution as every /sidebar route). Query params sessionId,
  dir, relativePath; the raw request body is streamed into the workspace.
- src/fs-operations.ts: writeWorkspaceUpload streams chunks to a temp
  sibling and renames into place, so a failed or oversized upload never
  leaves a partial file. Upload dir must sit inside the session cwd, the
  relativePath is sanitized (no '.', '..', empty or absolute segments) and
  the target must stay inside both.
- src/config.ts: uploadLimit (default 128MiB; oversized -> 413 too-large,
  a new wire code in src/wire.ts).
- client: send the File straight into the POST body (no base64 inflation)
  and cap at the same 128MiB (upload.ts, api.ts).

Tests: tests/fs-operations.spec.ts covers write, nested dirs, traversal /
containment refusal, the size cap, and no-partial-file on failure.
(pnpm test: only pre-existing node-pty/git sandbox failures, identical on
clean main; typecheck and build pass.)

* fix(host): harden upload containment, temp-file atomicity and stream failure cleanup

- relativePath: absolute paths and empty segments are refused (400), not
  silently collapsed or re-anchored (the spec's 'reject' semantics)
- temp file: uniquely named per request (randomUUID) so concurrent uploads
  to the same target stay independent; crashed-process leftovers never
  block later uploads (no pid reuse)
- write stream: permanent 'error' listener + error checks after every await
  so a failing disk rejects the operation instead of crashing the host or
  hanging the drain wait; the temp file is removed after the stream fully
  closes (Windows file locks), never leaving a partial file
- docstring now states the containment is lexical (symlink trust model)

* fix(explorer): thread upload error codes and cancellation through the client

- api.uploadFile/fetchUpload accept an AbortSignal (long-op convention);
  an aborted request rejects with the DOMException as-is instead of being
  wrapped into a 'network' error
- UploadResult carries the wire error code; the client pre-check now emits
  the same 'too-large' code as the host, and summarizeResults localizes
  'too-large' instead of showing the raw English 413 message
- uploadToDir stops the queue on abort: completed files stay, in-flight
  request is cancelled, nothing after is started
- uploadTooLarge copy no longer hardcodes a byte number (the host limit is
  configurable); MAX_UPLOAD_BYTES is documented as a mirror, not authority
- relativePathOf rejects absolute paths (isAbsolutePath) instead of
  stripping leading slashes, matching the host's 400 semantics
- progress text shared via uploadHintText for the hint and the overlay;
  success-hint duration is the exported UPLOAD_HINT_MS

* feat(explorer): full-window upload progress with cancel and VSCode-style drop targets

- UploadOverlay: blurred scrim + card over the file window (Modal token
  recipe: bg-mask-1 + mask blur, bg-layer-2, border-inverted, shadow-lv3),
  file-level progress bar, cancel button and Esc; scrim clicks do not abort
- TreePanel owns the upload session: header pickers, tree drag-drop and the
  context-menu picker funnel through startUpload; one session at a time
  (buttons disabled while busy, drops guarded); on settle the tree refreshes
  and the status hint shows success (3.5s, transient), failure or cancel
  (error color, stays)
- FileTree: drops on directory rows target that directory, drops on FILE
  rows target their parent (VSCode semantics, no more accidental root
  uploads), root row and body drop into the workspace root; drag-over
  highlights the target row (explorerRowDropTarget) plus the body outline
  and a hint pill (uploadDropHint / uploadTo) — raw-color inline outline
  replaced with the repo's dashed-accent classes
- upload status hint uses editorSearchHint + editorError like the search
  status; duplicated upload flow removed from FileTree (single caller)

* test: register the upload route in the smoke inventory

The new /sidebar/upload route joined the fenced route list, so the mount
assertion must expect it (the PR's CI gate was red).

* chore: bump to 0.14.1

Rebased onto main and cut a release version for the upload feature:
package.json / dsh.plugin.json / SIDEBAR_SERVICE_VERSION stay in sync
(test-guarded), README changelogs get the v0.14.1 entry.

* feat(explorer): keep the drop hint above DSH's page overlay and suppress its intake over the tree

DSH's whole-page image-drop intake (ui-conversation InputBar) listens on
document in the bubble phase: any 'Files' drag entering the page shows its
full-screen overlay (z-1000) and a drop anywhere adds images to the chat.
Our tree handled dragover/drop but not dragenter, so the overlay flashed
over the file window and drops outside the tree rows could leak to the
chat.

- TreePanel root now swallows dragenter/dragover/drop (preventDefault +
  stopPropagation) before the events reach document: the file window is an
  upload target, not an image target. Row/body handlers still fire first
  (bubble order) for their own targets; the root is the backstop for the
  search row, gaps and the search-results view.
- The drop hint pill is portaled to document.body at z-index 1001 — above
  DSH's overlay (z-1000) — so when a drag enters from outside the tree and
  DSH's mask lingers until dragend, our 'Drop files/folders here' /
  'Upload into {dir}' hint stays readable on top (transient decoration,
  pointer-events none; the drop still lands on the tree).

* feat(explorer): zone-split the drop overlays and fix the mask flicker over the sidebar

Sidebar.tsx's panel host now swallows the whole file-drag quartet
(enter/over/leave/drop, gated on the 'Files' type): DSH's document-level
intake keeps an enter/leave depth counter, and the leaves that escaped
the tree unbalanced it — that was the native full-screen mask flickering
over the sidebar. The region is now a black hole to that listener, so the
conversation column keeps DSH's native overlay and image intake while the
sidebar presents its own upload surface (drops over it never add images
to the chat).

The tree's drop affordance is now a pointer-inert frame portaled to
document.body at z-1001, anchored to the explorer body's viewport rect:
it replaces DSH's mask over the sidebar instead of competing with it, and
the hint pill docks at the bottom edge so the rows — the actual drop
targets — stay visible and aimable instead of sitting behind a centered
pill. Inside the tree, enter/leave depth counting (InputBar's own
pattern) replaces the relatedTarget check, which is unreliable for drag
events and flickered the row highlight on icon/label transitions; the row
target is now set authoritatively by the continuously-firing dragover.
dragleave handlers on rows are gone.

Polish: capsule hint pill with the upload icon and a float shadow, the
progress overlay fades in (the repo's dsh-row-in motion), its title gains
the icon, and progress numbers render tabular. tests/file-tree-drop.spec
pins the depth-counter behavior (child transitions keep the zone) and the
parent-directory drop semantics.

* feat(explorer): dim the whole viewport behind the tree's drop zone

The bottom-docked pill sat below the fold of attention; the zone frame
alone also failed to teach the left/right split — a drag over the sidebar
gave no hint that the conversation column still accepts drops (into the
chat, natively). The zone frame now carries the full-viewport dim itself
(a 200vmax box-shadow spread in DSH's own mask-drop token): everything
outside the tree's rect fades, the tree stays clear and aimable, and a
muted invitation centered in the space left of the zone (skipped under
200px) names the chat as the image target. The pill moves to the TOP edge
of the zone, right under the search row — the first thing the eye meets.

* feat(explorer): colorful drop heroes for both zones

The overlay is this flow's one brand moment, so it gets the color the
rest of the UI never does: hand-drawn SVG illustrations in the palette of
DSH's own native drop art (#3964FE/#679EFE/#9CE5ED) so the two zones read
as one family. The upload zone docks a hero at its top edge — a tray
illustration over the hint pill, stacked so the rows below stay aimable;
the chat zone's invitation becomes a tilted photo-cards card over its
label.

* feat(explorer): traverse dropped folders instead of reading dataTransfer.files

A dropped OS folder never surfaces in dataTransfer.files (that flat list
carries files only), so folder drops were silent no-ops while the UI
advertised 'drop files/folders'. uploadItemsFromDrop now captures entry
handles via webkitGetAsEntry — synchronously, since the dataTransfer enters
protected mode once the drop dispatch ends, while the captured handles stay
readable — then drains each directory's readEntries batches recursively,
prefixing every descendant's relative path. Entry-derived paths go through
the same sanitizer as picker paths; an entry that fails to read is skipped
rather than failing the whole drop, and the flat file list remains the
fallback where the entry API is unavailable. The FileTree drop handlers
ride the resulting promise.

---------

Co-authored-by: dsh local <dsh@local>
Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-20 18:28:57 +08:00
Atlas Zeng 5cc1a4b52e fix(layout): 推挤变量挂载期持续有效,拖拽松手后底边栏不再闪全宽 (#258) (#259) 2026-08-20 15:35:59 +08:00
Atlas Zeng e8d04980b4 fix(drag): 中断/快速释放不再回滚,HMR 后中心列重定位兜底 (#247 #248) (#249)
#247 拖拽回退(v0.13.1 无此问题,unified-host 重构引入的回归):
- pointerup 恢复 v0.13.1 语义:以 up 事件坐标(指针最终位置)提交,
  不再优先 rAF pending —— 浏览器把快速 flick 的尾部合并进 up 事件时
  pending 是陈旧中间值,提交后会“看起来没拖到位”
- onPointerCancel 把事件(携带坐标)传给 abortDrag:此前的坐标兜底
  是死代码(handler 未传 event),pointercancel 且无 move 时必回滚
- abortDrag 无任何位置信息时采用最后应用尺寸(lastDragSize),不再
  显式回滚到拖动前值;pointercancel -> lostpointercapture 双发只提交
  一次(dragCommitted 在所有提交路径置位,第二次中断不再回滚)

#248 HMR 后底栏空白/输入框位移:
- #root 观察改为 childList+subtree:HMR 原地替换 centerCol 节点时
  childList-only 观察器不触发,ref/RO 挂在已分离节点上永久失效
- locate 只在列节点身份变化时测量(同节点缩放交给 ResizeObserver),
  rAF 防抖避免流式渲染 mutation 突发强制布局
- 列节点断开(isConnected=false)时丢弃引用,下次 tick 重新定位
- 1.5s 兜底重定位 interval:任何 HMR teardown/setup 时序都能在数秒内
  收敛(style 属性字节相同、观察器尚未挂上等盲区全部覆盖)

测试:
- e2e 新增三条回归:pointercancel 双发提交一次、无坐标 capture-lost
  采用最后应用宽度、中心列深层替换后底栏重定位追踪新几何
- 修复既有快速拖动测试的滑入动画竞态(动画中读 strip 盒子在视口外,
  指针事件打不到 strip)——此前依赖时序侥幸通过
- 全部在新启动的 scratch DSH 实例(e2e-mount,随机端口)上验证,
  未触碰本地 3080 实例;三条新用例对未修复代码均失败(回滚/不恢复)
2026-08-20 14:58:56 +08:00
laoyh 5be195df18 fix(mobile): make unavailable sidebar state clear 2026-08-20 12:13:49 +08:00
Atlas Zeng 6c891514b5 feat(injection): 统一面板宿主注入重构——含块层+几何写路径+chunk缓存+锚点弱化+桌面/移动适配 (#232)
* feat(injection): 统一面板宿主——固定含块层 + 挂载自检降级 + body 锚点守卫

- Sidebar 渲染根包 [data-dsh-panel-host](fixed inset-0 pointer-events:none)
- 面板/开关簇由 fixed 改 absolute(层内视口坐标等价),免疫套壳中间层 transform 劫持
- 挂载后一次性几何自检:页面级 transform 时降级 absolute + rAF 视口同步
- MutationObserver(body childList)守卫锚点被宿主清空时重挂

* perf(injection): 几何写路径统一为 writeGeometry + 面板 layout 隔离

- 拖拽帧与打开态共用单一推挤写入器(--dsh-sidebar-width/height)
- .panel/.bottomPanel contain: layout style——内部重排不外泄
- 拖拽期(body[data-dsh-sidebar-dragging])才挂 will-change: transform

* perf(chunks): HMR 激活保留未变 chunk 的已解析 exports + ETag HEAD 重验证

- 新增 revalidateChunksOnReactivate():激活时 HEAD 每个已加载 chunk 对比 ETag,
  未变保留内存缓存(跳过重注入/重执行),变更或不可达则 fail-open 重取
- 测试夹具与孤儿缓存条目不跨激活存活
- index.tsx 激活路径由 resetChunks() 改为 revalidateChunksOnReactivate()

* fix(layout): 推挤锚点改 data 属性 + #root calc 宽度防桌面壳溢出

- nth-child(2) → #root [data-dsh-frame] > [data-pane="conversation"](实证同元素)
- #root 宽度 calc(100% - var) 与 margin 同步过渡:web 视觉不变(实测 1140+300=1440 无溢出),
  Desktop 壳(#208)width:100% 的加性溢出根因消除

* feat(desktop): 桌面信号自动探测 + win32 顶栏 overlay 自动避让 + 键盘/safe-area 适配

- 新增 desktop-env.ts:解析 dsh-desktop-mode/platform URL 信号与 preload 标记
- win32 advanced 壳自动启用 titleBarCompat(32px overlay),手动 pref 仍可覆盖
- visualViewport 键盘 inset:底部面板/窄屏抽屉键盘弹起时上移
- 面板与开关簇 safe-area-inset 适配(env() 桌面端为 0)
- 单测 7 例(含 URLSearchParams 前导 ? 剥除修复)

* docs+e2e: 注入方案实施记录(路线B关闭、偏差记录、基线数据)+ 契约同步 + e2e 新断言

- 设计文档:路线 B 标记关闭、§8.1 改单 PR 步骤(含 commit 对照)、拖拽基线数据、实施偏差记录
- AGENTS.md §8:data-dsh-panel-host 含块层、推挤锚点契约(禁 nth-child)、桌面信号
- mount.e2e:data-dsh-panel-host 断言 + win32 advanced 桌面信号场景

* fix(cr): 响应 #232 CR 三条 findings

- P1 降级同步:按未修正几何判定(跟踪自身补偿量),祖先 transform 消失才退出循环——
  修正后矩形不再被误判为无需修正(自清除缺陷)
- P1 chunk 重验证成为 loadChunk 屏障:重验证挂起期间不提供缓存,杜绝 HMR 竞态陈旧 exports;
  测试夹具加载不再被误记为生产加载
- P2 键盘 inset:innerHeight - (vv.height + vv.offsetTop),并监听 scroll(offsetTop 变化)
- 新增屏障单测 1 例;设计文档偏差记录更新

* fix(chunks): 屏障 HEAD 加超时兜底 + resetChunks 清 revalidation

- revalidateChunksOnReactivate 的 HEAD 与 recordEtag 均带
  AbortSignal.timeout(5s):路由挂起时 fail-open(drop + 重取),
  屏障不再可能无限期阻塞所有懒加载 chunk(CR #232 P1 修复的健壮性缺口)
- resetChunks 同步清 revalidation:重验证挂起期间重置后,
  loadChunk 不再等待已丢弃状态的孤儿任务(finally 身份判断 no-op)
- 单测 +2:HEAD 挂起超时 fail-open(fake AbortSignal.timeout 触发中止)、
  resetChunks 清屏障后 loadChunk 立即放行

* fix(deps): align @deepseek-ai/* peers with DSH 0.1.0-rc.8

- peer/devDependencies: 13 个 @deepseek-ai/* 从 ^0.1.0-rc.7 升至
  ^0.1.0-rc.8;删除随 rc.8 消失的 dsh-client-web-react / dsh-client-schema-form
  (无 rc.8 发布、rc.8 依赖树与壳 bundle 零引用、插件 src 零引用);
  cordis peer/devDep 同步升 ^4.0.0-rc.8
- 版本 0.13.1 -> 0.14.0(package.json / dsh.plugin.json /
  SIDEBAR_SERVICE_VERSION 三处同步)
- CHUNK_EXTERNALS / CLIENT_EXTERNALS / CLIENT_REQUIRE_ALLOWED 三处镜像
  同步删除两个死包(primitives/slots 仍是 rc.8 壳静态模块表种子,保留)
- CI 挂载冒烟钉版 @deepseek-ai/dsh@0.1.0-rc.7 -> rc.8
- pnpm clean --lockfile 全量重解析:lockfile 内 @deepseek-ai/* rc.7 残留
  清零(传递链同升 rc.8,杜绝 #206 式混用树);pnpm-workspace.yaml
  minimumReleaseAgeExclude 按 #207 模式整体换为 rc.8 列表
- 文档同步:README 安装说明、AGENTS.md CI 钉版、外部插件指南与设计文档
  cordis 示例、issue 模板版本示例

* feat(prefs): editorExplorer 默认独立(关)——文件各自新开 tab

- SIDEBAR_PREFS_DEFAULTS 与 host PrefsSchema 的 editorExplorer 默认
  true -> false:新会话树点击/打开文件按 path 新开 tab,无路径窗口即
  纯资源管理器;合并模式保留为可选手动开启(持久化过的用户设置不受影响)
- 测试同步:prefs.spec 默认断言翻转、plugin-shape.spec 与 smoke.spec
  默认对象翻转;editor-host 合并模式用例显式开启 editorExplorer(split
  用例本已显式);side-card-section-rows 的 select 行用例固定选中 Merged
- e2e 重写:mount.e2e 文件打开流程从 in-place 断言改为独立模式
  (新开 hello.txt tab、Files home tab 保留),文件头注释同步
- 契约文档同步:AGENTS.md §3.4 editor 行默认描述翻转 + CI 冒烟描述、
  README/README_EN v0.13.0 特性描述 + v0.14.0 changelog

* fix(client): rc.8 模块系统经 ctx.modules 注入 + chunk 副本全局共享

rc.8 不再暴露 window.__DSH_MODULES__ 页面全局(改由 ctx.modules 服务
提供),chunk 外部依赖解析全面失效(terminal/editor/mermaid 全部
"client module system unavailable")。修复:

- src/client/index.tsx: inject 增加 'modules'(Cordis 服务访问守卫,
  此前直接读 ctx.modules 抛 "cannot get property modules without
  inject");apply() 激活时 setChunkModuleSystem(ctx.modules)
- package.json dsh.client.inject 增加 @deepseek-ai/dsh-client-modules
- chunk-loader: 注入写插件自有页面全局 __dshSidebarModuleSystem__——
  懒加载 chunk bundle 各自内联一份 loader(rc.7 靠官方全局回退工作,
  rc.8 全局消失后副本注入变量恒空);moduleSystem() 按
  注入变量 → 插件全局 → rc.7 全局回退解析
- context-types: Context 镜像补 modules 服务声明(client half)
- e2e: assertNoCrash 失败时输出错误条文本;独立模式断言修正
  (Files 数量按存在性、mermaid 流程先切回 Files、md 预览 locator 去掉
  重复的 [data-dsh-better-sidebar] 嵌套)

验证:typecheck / 639 测试全绿;本地 rc.8 CLI + Chromium 挂载冒烟 3/3
通过(drag-layout、mount 深扫含 editor/mermaid chunk、桌面信号)

* feat(settings): 终端 shell/shellArgs 可在设置页配置(bash 管理)

- SidebarPrefs 新增 terminalShell / terminalShellArgs(默认空 = 保持
  yaml config.shell → $SHELL / 登录 shell / powershell.exe 的既有解析)
- 终端卡齿轮二级页面新增两行 text 配置(与字体行同列)
- host 侧生效链路:UI 终端 WS 打开与 agent terminal_create 均读取
  设置文档覆盖(ptyManager.open / AgentPtyRegistry.create 增加可选
  shell 覆盖参数;shellOverridesOf 解析设置文档);设置页写入即时
  影响之后打开的终端(既有 pty 保持原 shell)
- locales zh/en 文案;parsePrefs 校验新字段;测试同步

* feat(settings): 侧边卡片设置页加 DSH-better-sidebar 版本徽标

- 设置页 intro 下新增身份徽标:名称 + v{service.version}(服务实例
  版本,与 package.json 由 tests/service.spec.ts 保持同步)
- token 驱动样式(--dsw-alias-*),名称/版本 pill 布局

* fix(drag): 快速拖动提交最后位置 + HMR 后中心列重定位兜底

快速释放(浏览器合并/丢失 pointermove 突发,超快速 flick 甚至可能
完全无 move)时的回退问题:
- pointerup 优先采用最后一次已知拖动值(rAF pending),而非 up 位置
  (事件合并时可能等于按下位置)
- pointercancel / lostpointercapture 中断时同样提交最后已知位置;
  无 move 时用中断事件自身的坐标计算(clamp 后提交)——只有真正
  未产生位移的拖动才回滚
- commitDrag 统一 flush + 同值提交 + 立即重测中心列(消除提交后
  React 用旧 centerRect 覆盖底栏 right 的中间帧)

HMR 重激活后底栏空白(输入框被推挤上移、强制刷新恢复):
- 根因:新组件实例重跑 locate 时壳的 centerCol 尚未就绪,而壳重建
  复用 #root 子节点(childList 观察器不触发)→ centerRect 卡 {0,0}
  → 底栏 visibility:hidden 永久空白
- 修复:额外观察 <html> 的 style 属性(layout push 写回 --dsh-sidebar-*
  时重定位重测);底栏打开时 effect 重跑 locate/measure 兜底
- 底栏未测量时由「不渲染」改为 visibility:hidden(渲染链不被几何
  门控,jsdom 测试不回归)

e2e 新增:极快拖动(单 move 立即 up)提交拖动位置、稳定后不回退

* feat(plugins): 添加插件目录支持搜索/分组/独立滚动(多插件场景)

- PluginListBody 顶部加实时搜索框(按名称/id/描述过滤,空结果提示)
- PluginEntry 增加可选 category 分组字段(i18n 友好),目录条目按组
  渲染(未分类条目保持平铺);数量徽标跟随过滤结果
- 条目列表 max-height + overflow-y 独立滚动,插件多时弹窗不再无限
  增长;渲染数量由搜索天然收敛
- locales zh/en(搜索占位、无匹配);测试文件固定英文环境
  (vitest 4.1.11+ jsdom 跟随系统 locale,本机 zh-CN 导致英文文案
  断言漂移)

* chore(deps): 适配 pnpm 11.8 supply-chain 校验(exclude 通配)

pnpm 11.8.0 起对 lockfile 既有条目强制 minimumReleaseAge 校验,
且精确 exclude(@scope/pkg@version)对带 peer 依赖后缀的解析条目
匹配失效(44 条误报违规,CI 全红)。@deepseek-ai/* scope 为官方
可信 scope,minimumReleaseAgeExclude 改为 '@deepseek-ai/*' 通配
(保留 rc.1 历史与 stable 条目);通配经实测通过全部 504 条校验。

* docs: v0.14.0 changelog 补全(自 v0.13.1 全部更改 + 特别声明 rc.8 适配)

新增条目:终端 shell/shellArgs 设置页配置、设置页版本徽标、
添加插件目录搜索/分组/滚动、rc.8 模块系统迁移、拖拽健壮性修复;
版本说明块醒目声明本版适配 DSH 0.1.0-rc.8(rc.7 及更早无法解析依赖)
2026-08-20 02:09:36 +08:00
CI Test 23c315ed82 fix(markdown): use manual preview refresh 2026-08-19 22:05:55 +08:00
CI Test c0e41f4b0d fix(markdown): refresh previews after external changes 2026-08-19 12:39:55 +08:00
MXY 4c875ceff7 fix(editor): 预览刷新三件套——手动刷新按钮 + 切预览自动重载 + 预览保存后重载(fixes #167)
A. header 新增刷新按钮(toolbar 非空时显示),经 reloadSeq 重跑加载 effect;B. 编辑→预览切换时自动重载(dirty 或保存失败则抑制,避免丢草稿);C. 预览模式下 saveState 进入 saved 边沿触发一次重载。

新增 tests/editor-refresh.spec.tsx 4 用例(A 重载计数 / B 正常与 dirty 抑制 / C saved 边沿);全量 622 通过(其余 8 失败为既有 Windows 平台问题:pty-deps/side-card 的 AttachConsole)。
2026-08-19 04:04:44 +08:00
MXY 8181e13b52 fix(trust-fence): Origin 校验改按 hostname 比较,对齐 DSH 网关栅栏(Edge 151 无端口 Origin 修复)
镜像栅栏此前按 host(含端口)比较 Origin,而部分 Chromium 构建(Edge 151)会把非默认端口 loopback 页面的 Origin 序列化为不带端口的形式,导致 /sidebar/* 在 Edge 151 下全部 403 forbidden。DSH 官方栅栏(api-request-trust.ts)已改为 hostname 比较,本提交同步该语义:端口不再重决信任,Host 栅栏与 sec-fetch-site 拒绝逻辑不变。

测试:tests/trusted-hosts.spec.ts 新增无端口 Origin 放行、不同 hostname 拒绝、null origin 拒绝三个回归用例;本地 scratch 挂载 + curl 实测通过。
2026-08-18 23:23:17 +08:00
SolitudeandMenghuan1918 bcd60234ac fix(sidebar): unstick interrupted panel drags so the bottom panel keeps tracking layout (#183)
* fix(sidebar): unstick interrupted panel drags so the bottom panel keeps tracking layout

A width/height/corner drag whose pointer stream ends without pointerup
(pointercancel, or pointer capture lost prematurely — e.g. a trackpad
gesture interrupting the drag) left draggingWidth/draggingBottom/
draggingCorner stuck true: onPointerUp guards on hasPointerCapture and
never ran, and no onPointerCancel/onLostPointerCapture handler existed.

The stuck drag state pauses center-column measurement (draggingRef)
permanently, so centerRect goes stale and the bottom panel keeps frozen
inline left/right edges — it no longer expands when the right sidebar
collapses or the app rail toggles until a full reload.

Fix: mark each drag uncommitted on pointerdown and committed in
pointerup before releasing capture; the new pointercancel /
lostpointercapture handlers abort an uncommitted drag by reverting the
DOM and layout variables to the store's committed sizes and clearing
the dragging state, which resumes measurement.

* fix(sidebar): clamp abort-drag rollback to collapsed/narrow geometry (P2 review)

abortDrag wrote the committed width even when the right panel is
collapsed or the viewport is narrow, where the layout-push effect had
already written 0 to --dsh-sidebar-width — an interrupted drag then left
the app shell squeezed by an invisible sidebar. Mirror the layout
effect's clamping: write 0 when narrow or the panel is closed.

---------

Co-authored-by: Menghuan1918 <menghuan2003@outlook.com>
2026-08-18 23:08:54 +08:00
Cold Cloud 76aacddbb9 fix(tab-bar): middle-click close lost to autoscroll / release drift (#145)
The close settled on auxclick delivery, which the browser redirects to the
nearest common ancestor of the press/release targets when they differ — any
drift, middle-button autoscroll scroll, or tab-list reflow between press and
release swallowed the close (the strip hides its scrollbar, so Chrome's
autoscroll indicator is inert here and only added confusion).

Now the press target is recorded on middle mousedown (preventDefaulted to
disarm autoscroll at the source) and the close settles on the first middle
mouseup OVER that same tab — release-position semantics matching VS Code
(microsoft/vscode#101028) and user expectations for Chrome tabs
(crbug/40679924): pressing on a tab and releasing elsewhere cancels.
Left-button activate/drag and wheel scrolling are untouched
(tests/tab-bar-middle-click.spec.tsx).
2026-08-18 23:04:29 +08:00
Atlas Zeng e8757026a1 fix(deps): align @deepseek-ai/* peers with DSH 0.1.0-rc.7 (fixes #206) (#207) 2026-08-18 21:41:28 +08:00
NuperAki c973d6ec1b feat(terminal): 支持配置 shell 与 shellArgs,终端标题使用 shell 名 (#125)
- 支持通过 cordis.patch.yml 指定终端 shell
- 支持 shellArgs 带参启动终端 shell
- 终端标题改用 shell 名,内部使用 uuid 管理
2026-08-18 19:56:04 +08:00
ShelbyTang 39666832a2 fix: 文件编辑器支持 Vue 与 28 种 legacy 语言语法高亮
- .vue 经 @codemirror/lang-vue 解析,显式 nestedLanguages 仅补 lang=tsx/jsx 与 <style lang=scss/sass/less/stylus> 分派,其余由 lang-html 默认 defaultNesting 覆盖
- 零新依赖扩表 28 个 legacy-modes 语言(scss/ruby/lua/perl/diff/protobuf 等),.v/.m 因跨语言歧义故意不映射
- languageForPath 增加工厂异常防御,降级纯文本而非崩溃
- 测试新增完备性回归门(switch↔FACTORIES 双向差集 + 逐扩展构造)与 SFC 行为用例
2026-08-18 18:05:01 +08:00
ShelbyTang dd5a3dda07 fix(terminal): 补全 Nerd Font 图标字体回退,修复提示符图标显示为豆腐块
starship / powerlevel10k 等提示符的图标取自 Nerd Font 私有区。Chromium 的
隐式系统字体回退能覆盖 BMP PUA(U+E000–U+F8FF,如 U+E0B0 powerline 分隔符),
但覆盖不到补充平面 PUA-B(U+F0000+)——Nerd Fonts v3 把 Material Design 图标集
迁到了这里,于是这部分图标渲染为豆腐块。经排查用户字体本身含完整字形(format-12
cmap 覆盖上述码点),而 macOS 系统等宽字体(Menlo / SF Mono / Monaco)对这些
码点零覆盖,根因是插件的 fontFamily 栈未显式列出任何 Nerd Font。

修复:新增 withIconFontFallbacks(),在 resolveTerminalFont 出口为胜出的基础
字体追加 Nerd Font 图标族(显式命名可使浏览器逐字符查询,两个平面都能命中)。
基础字体优先级链(用户 pref > 主题 > 内置栈)保持不变。

三条约束:
- 只追加不前置:xterm 用字体栈首项测量单元格宽度,基础字体须留在最前;
- 插入到第一个通用族(monospace 等)之前:通用族是 catch-all,其后的字体
  永不被查询,仅判断尾项会让主题给出的 `ui-monospace, Menlo, monospace, …`
  这类栈静默失效;
- 按族名去重(引号/大小写/空白不敏感),用户自列的字体保持原有优先级。

另外:
- 过滤 CSS 全局关键字(inherit/initial/unset/revert/revert-layer)。主题字体
  取自原始 token 值,皮肤确实会设成这类值,追加后会得到非法 font-family 被
  CSSOM 静默丢弃,导致主题字体与图标字体一起失效,现改为回退到下一级;
- 字体栈按顶层逗号切分并感知引号与括号,避免拆坏 "Foo,Bar" 与 var(--x, y);
- 不列彩色 emoji 字体:本问题的图标全为 PUA 且真 emoji 有独立回退路径,而
  彩色字体会捕获提示符依赖等宽的 BMP 符号(U+26A0、U+2714 等)破坏网格。

单测 12 → 19 例,补齐中间通用族回归守卫、CSS 全局关键字、多通用族、幂等性与
回退顺序契约;全量 594 例通过。
2026-08-18 10:50:30 +08:00
Qiuner 4d0f99ced6 fix(html): declare UTF-8 preview encoding (#170) 2026-08-18 10:29:31 +08:00
Atlas Zeng 5e416eccef feat(mermaid): Markdown 预览安全渲染 Mermaid 图表(strict 清洗 + 懒加载 chunk + 点击放大) (#164)
* feat(mermaid): 新增 mermaid 懒加载 chunk(构建链四处同步 + mermaid 依赖)

tsdown CHUNKS / bundle-route CHUNK_NAMES / chunk-loader ChunkName /
package.json files 同步注册 mermaid chunk;mermaid ^11.16.1 进依赖与
lockfile;chunk 构建注入 uuid 浏览器入口别名,绕开 node:crypto 纯度门。

* feat(mermaid): Markdown 预览安全渲染 mermaid 图表 + 点击放大弹窗

- splitMermaidBlocks 纯函数按 fence 切块,无 mermaid 文件零行为变化;
- mermaid.initialize 走 securityLevel strict + htmlLabels:false,不应用
  bindFunctions(静态图);渲染 SVG 经 mermaid-sanitize 二次清洗:
  删 foreignObject/script/外来 HTML 元素、剥 @*/on* 属性、移除全部
  href/xlink:href,仅接受 SVG 根,解析失败返回空串;
- 点击图表弹出放大弹窗(滚轮缩放/拖拽平移/快捷键,借鉴 PR #75 交互,
  基于已清洗 SVG 克隆重写,不新增攻击面);
- 深浅色跟随重渲、失败回退错误条 + 原码、CodeBlock 同款 chrome。

* test(mermaid): splitter/sanitize 单测 + chunk 清单同步 + 挂载冒烟 diagram.md

- mermaid-blocks.spec 10 例 fence 切块语义;
- mermaid-sanitize.spec 8 例 XSS(foreignObject/on*/@*/href/xlink:href/
  script/外来元素/畸形 XML/非 SVG 根);
- bundle-route / chunk-artifact / manifest-consistency 清单加入 mermaid
  (chunk-artifact 补 addEventListener stub);
- 挂载冒烟新增 diagram.md:client-mermaid.js 往返、SVG 节点文字、
  点击弹窗 Esc 关闭、preview 模式编辑器隐藏。

* docs(mermaid): 安全渲染设计文档 + lazy-chunks 表 + README 双语更新

设计文档记录基线决策(PR #99 架构 + PR #75 点击放大)、安全模型
(strict/htmlLabels/清洗/href 剥离/无 bindFunctions)、验证与取舍。

* fix(mermaid): sanitizer 属性名大小写归一,堵住混合大小写属性绕过(CR P1)

XML 属性名保留大小写,而输出会经 HTML 解析归一化(dangerouslySetInnerHTML),
oNload/HREF/xlink:HREF 这类混合大小写属性此前会漏过清洗。比较前先
toLowerCase(),按原名删除;新增混合大小写事件/链接属性回归用例。

* fix(mermaid): sanitizer 元素黑名单大小写归一,堵住混合大小写元素绕过(CR P1)

XML 元素名保留大小写,querySelectorAll('script') 等选择器匹配不到
<sCrIpT>/<foreignobject>,而输出经 HTML 解析会被归一化为 SVG
script/foreignObject(可执行/可注入 HTML)。改为遍历全部元素按
localName.toLowerCase() 命中小写黑名单后整节点删除;新增混合大小写
script/foreignObject/外来 HTML 元素回归用例(jsdom 实证:旧选择器
命中 0,新逻辑命中 2)。

* fix(mermaid): 单次 Markdown 解析 + 占位交换(CR P1)+ CommonMark fence 规则(CR P2)

P1(跨 fence 语义):弃用逐块 MarkdownText 拆分,改为整篇文档单次
MarkdownText 渲染,布局后把 language-mermaid 代码块交换为 MermaidDiagram
(React 管理的 .md-code-block 宿主保留在树中、display:contents 抑制盒子,
协调安全;块不再 mermaid 时还原原 children)。新增
tests/mermaid-markdown.spec.tsx 与 e2e 跨 fence 引用链接断言。

P2(fence 匹配):支持 3+ 反引号与波浪号开 fence;闭 fence 须同字符且
长度不小于开 fence;反引号 info string 含反引号不视为开 fence;修复
带缩进开 fence 的 info 提取。blocks 单测 +3 例。
2026-08-18 01:22:28 +08:00
Atlas Zeng 9ad0a49b8a feat(settings): mutual exclusion with the dsh-web-ui aionui-panel provider (v0.13.0) (#181)
Reads the aionui-panel settings namespace's rightPanel provider choice: while
it resolves to 'aionui-panel', the whole sidebar does not mount (host
settings.get reports externalDisable, the client mount gate skips mounting
and the link/open-path/turn-tail takeovers step aside). Live via the
settings/document-updated push (best effort; deployments without the
'remote' service fall back to boot-time evaluation). No new user-facing
setting — the aionui panel card remains the single source of truth.
2026-08-18 00:44:22 +08:00
Atlas Zeng 6ee1ba11d0 feat(editor): 文件窗口与资源管理器二合一 + 声明式设置 select 行 (#151)
* feat(editor): merged explorer mode — path input, docked file tree, global name search

New editorExplorer pref (default on, editor card gear): the editor tab gains
a path-input header and a toggleable right-docked tree panel (per-tab state
in tab.meta.treeOpen; file tabs default closed, the seeded path-less home
tab defaults open). Fresh sessions seed that empty editor tab instead of
the explorer tab when merged mode is on; the standalone explorer tab stays
in the + menu unchanged.

- Extract FileTree.tsx from ExplorerView (shared by both hosts)
- Host fs.search route: recursive case-insensitive name search rooted at
  the session cwd, budget-capped (200 matches / 100k visited), skips .git,
  never descends symlink dirs
- EditorHost reads the pref reactively (boolean snapshot) and keeps the
  pre-merge layout verbatim when off

* feat(settings): select row type for declarative settings; editorExplorer as iconed select

- SidebarSettingToggle gains type 'select' with options {value,title,desc,icon}
  and multi (default false; stores the picked values as an array in options
  order). Any option carrying an icon renders big-icon option cards in the
  dropdown and the icon in the closed anchor; icon-less rows are one-line text.
- SelectRow in SideCardSection rides the primitives Menu (portal); single-pick
  commits and closes, multi toggles and stays open. Wired for both host-prefs
  toggles and plugin-owned pluginToggles via the new onSelectValue seam.
- The editorExplorer merged-mode pref is now an iconed select (merged vs
  separate) instead of a switch; the stored value stays boolean.
- Advertise the capability as 'settingSelect' in SIDEBAR_FEATURES.

* feat(editor): unify explorer into the editor surface, resizable tree dock, hoisted toolbar

- One tree implementation, two presentations: the shared TreePanel (global
  name search + refresh + FileTree) docks as the editor tab's right panel
  and — in merged mode — fills the explorer tab's window; the pref-off
  explorer keeps its legacy header shell.
- The docked panel drag-resizes from its left edge (160-480px clamp),
  persisted as tab.meta.treeWidth alongside treeOpen.
- The text editor's preview/edit/save toolbar moves into the path-input
  header row: FileViewerProps gains internal toolbar fields ('host' mode
  skips the viewer's own row, reports EditorToolbarState and registers
  EditorToolbarControls); non-merged mode is byte-identical to before.

* feat(editor): single files window with in-place switching; explorer descriptor removed

- editor tab replaces explorer on the + menu (order 10, Files); persisted
  explorer tabs migrate to the editor home tab via sanitizeState
- editorExplorer now only controls open behavior: merged = in-place
  updateTab, independent = openSidebarFile; both modes share the merged
  chrome (path input + hoisted preview/edit/save toolbar + tree toggle)
- file tree context menu gains 'open in new tab' / 'open to the side'
  (mintTabId avoids the id safety net; insertLeafAt splits right)
- tree dock resize uses pointer capture (touch-action none, 6px handle)
- defaults: openByDefault off, sidebar width 35%
- file tree clips horizontal overflow (no sideways scroll)
- e2e: mount lane arms the editor-chunk wait before goto and clicks the
  tree row's left edge (hover @-reference button sits mid-row); drag lane
  expands the panel first (collapsed push writes 0px), waits for the
  slide-in to settle, and picks the leftmost col-resize strip; 'Files'
  title assertions require draggable to skip the bottom panel's welcome
  cards

* feat(editor): split mode turns the path-less window into the standalone explorer

In independent (split) mode a path-less files window renders ONLY the
tree panel (search + FileTree, full-window form) — no path input, no
hoisted toolbar, no tree toggle. File clicks open per-path editor tabs
(full chrome, tree docked closed by default) through openSidebarFile,
unchanged. Merged mode is untouched (chrome + docked tree, in-place
switching). TreePanel's previously reserved 'full' prop now has its
consumer.
2026-08-17 15:00:03 +08:00
Atlas Zeng 8d9ab1cd2f Merge pull request #147 from omdsh-dev/fix/node-pty-load-failure-repair
fix: node-pty 加载失败降级为终端修复提示,不再拖垮 server(#140)
2026-08-17 00:06:20 +08:00
yq a0918496f3 Merge pull request #91 from yq04/feat/catalog-dsh-git-remotes
Recommend dsh-git-remotes in the tab plugin catalog
2026-08-17 00:05:24 +08:00
Menghuan1918 be95787c00 fix: symlink 探测有界并发 + UNC 标记平台中性化(回应 CR/自审)
- fs-tree: symlink 目标 stat 从串行改为 32 并发工作池(readdir 流关闭后统一
  探测),symlink 密集/网络目标目录不再累加延迟;顺序与分类语义不变
- html-route: 解码不再重建反斜杠 UNC,统一重建为平台中性的 //server/share/...
  前斜杠形态(win32/POSIX 均正确 resolve),从而删除 windowsPathStyle 平台信号
  与 client isWindowsStylePath 助手;同时解决无 cwd 时正斜杠 UNC 的往返盲点
- 测试/文档同步;移除未使用的 isPosix
2026-08-16 23:35:57 +08:00
Menghuan1918 9652bbda59 fix(html): 正斜杠 UNC 路径在匹配正斜杠 cwd 时也保留标记(回应 Codex P2 #2)
当 cwd 与产出路径都是 //server/share/... 形式时,此前两个
isWindowsStylePath 都为 false 而漏掉 '//' 标记,win32 解码后落在当前
盘符被 isWithin 拒绝。现按 review 建议:'//' 路径仅在其 '//' cwd 匹配时
视为 UNC;POSIX 会话的 '//' 绝对路径仍保持无标记。
2026-08-16 23:24:53 +08:00
Menghuan1918 0052061a72 fix(html): UNC 标记信号纳入文件路径本身(回应 Codex P2 review)
cwd 未水合或为正斜杠形式时,仅凭 scope.cwd 会漏掉反斜杠 UNC 路径的
'//' 标记,win32 上预览被 isWithin 拒绝(403)。htmlUrl 现在同时以
isWindowsStylePath(path) 判定:明确的反斜杠 UNC 路径始终保留标记;
'//' 正斜杠路径仍只由 cwd 形态决定(POSIX 不受影响)。
2026-08-16 23:24:50 +08:00
Menghuan1918 db189e3852 feat: 统一路径处理——UNC/软链接分类/html 路由平台守卫(#65 #67 #43 #79 #115)
- fs-tree: requireAbsolute 改用 path.isAbsolute(win32 UNC 放行、C:foo 仍拒);
  目录列表仅对 symlink 行 stat 一次,暴露 isSymlink/broken,目录软链接可展开
- html-route: UNC 路径经 '//' 标记编码往返;标记仅在 Windows 风格会话启用,
  POSIX '//' 绝对路径不再被误判(平台守卫)
- client: isAbsolutePath/isWindowsStylePath 镜像 + resolveSidebarPath 识别 UNC,
  htmlUrl 以会话 cwd 形态传递平台信号;FsEntry 增加 isSymlink/broken
- explorer: 软链接行链接徽标、悬空链接标红 + tooltip(issue #65)
- 测试: tests/platform.ts 守卫助手;win32 专属用例改为 skipIf 可见跳过,
  补 POSIX 反向分支与 UNC 矩阵;新增 fs-tree-symlink.spec
2026-08-16 23:24:43 +08:00
zemul 5c138ca851 catalog: add dsh-video-preview (video previewer) to the file-previewer recommended plugins (#126)
* catalog: add dsh-video-preview to the file-previewer recommended plugins

Adds the video preview plugin (dsh-video-preview) to the "add preview plugin" modal catalog, with zh/en descriptions. Data integrity is guarded by tests/plugin-list.spec.ts.

* test: make add-plugin-modal window.open count catalog-driven

The test pinned the viewer catalog to a single entry (2 "Open:" buttons).
Adding a second recommended plugin (dsh-video-preview) renders 4 buttons
and broke the hardcoded assertion. Derive the expected count from
builtinViewerPlugins so adding/removing plugins keeps the test honest.
2026-08-16 22:32:17 +08:00
Atlas Zeng fc4031e65f fix: 迁移弃用的 xterm 依赖至 @xterm/xterm(Closes #122) (#128)
* fix: 迁移弃用的 xterm 依赖至 @xterm/xterm(#122)

- dependencies 移除 xterm@^5.3.0(已弃用)与 @xterm/addon-fit(二者始终
  内联进 lib/client-terminal.js 懒加载 chunk,运行时零依赖)
- 二者移至 devDependencies(构建期仍需:tsdown 内联 + CSS 构建期解析 + 类型)
- 代码与构建注释改用官方新包名 @xterm/xterm@^5.5.0(addon-fit@0.10.0 的
  peer 约束锁 ^5.0.0,不用 6.x),CSS 路径同步为 @xterm/xterm/css/xterm.css
- 消除用户安装时的 deprecated 警告与 missing peer @xterm/xterm 警告

* chore: 顺带升级 @xterm/addon-fit 至 ^0.11.0

- 0.11.0(2025-12 发布)不再声明 peer 依赖,消除 peer 解析噪音
- 其运行时 API(_renderService.clear / options.overviewRuler 兜底)与
  @xterm/xterm@^5.5.0 兼容:overviewRuler 在 5.x 为 undefined 时回退 14,
  与 0.10.0 行为一致;typecheck/build/test(515)/ 挂载门禁均通过
2026-08-16 19:14:57 +08:00
chalei1122-cell 165ab03ea6 fix(sidebar): drag-release bounce, session-delete terminal cleanup, silent resize
- Sidebar.tsx: useLayoutEffect for the layout-push variables so cleanup+setup
  land in the same pre-paint frame (no full-width flash on drag release);
  applyDrag writes the width only while the panel is open (a closed right
  panel no longer squeezes the conversation when dragging the bottom height);
  flush the final drag frame before stopDragScheduling on all three handles;
  sync centerRect.right on width/corner release so the bottom panel does not
  jump.
- index.ts/pty-manager.ts: close a session's UI terminals immediately on
  session/disposed (they used to linger until reconnectGraceMs expired).
- sidebar.module.css: no highlight on the resize handles while dragging
  (local preference; the corner hover hint stays).
2026-08-16 17:43:29 +08:00
Atlas Zeng 3c196fffc3 feat: 皮肤兼容(令牌驱动)+ CSS 修复(#106 #105 #90 #60,附带 #52 #57 #92) (#110)
令牌驱动方案(主目标:与 dsh-web-ui 皮肤中心兼容):
- 面板表面改用通用卡片令牌 --dsw-alias-bg-layer-1,不再消费宿主左导航专属
  --dsw-specific-sidebar-fill——dsh-web-ui 10 款皮肤全部覆盖 --dsw-alias-*
  层,换肤后侧边栏自动跟随,零每皮肤适配(#60/#105/#106)。
- effectiveTokenValue 增加 alpha 阈值(新增 colorAlpha 解析 computed 颜色):
  transparent 与 alpha<0.9 的半透明玻璃值(dsh-web-ui 皮肤 bg-base 为
  rgba 0.16–0.7)一律回退不透明底色,终端/编辑器文字永不叠在皮肤背景画上
  (#90);≥0.9 的近不透明值放行,皮肤仍可控终端表面。

独立修复(保留):
- z-index 降到 DSH 浮层栈(100+)之下:面板 40 / 按钮簇 45 / 角手柄面板内
  局部 2,Cordis 弹出框不再被底部工作台遮挡(#52)。
- 角手柄移入右面板、CSS 相对定位(bottom: calc(var(--dsh-sidebar-height)
  + 6px)),删除 JS 内联 viewport 坐标。
- 刷新按钮统一 14px(#57 前半)。
- 拖拽逐帧回归 e2e(#92):真实挂载下 rAF 采样(拖条以 cursor: col-resize
  语义定位),断言拖拽期间 data-dsh-sidebar-dragging 存在、#root transition
  为 none、会话列与面板边单调 1:1 跟随(实测通过)。

按 KISS 回退首版契约:类名 kebab-case 改名与 data-bs-* 语义钩子全部移除
(令牌驱动下不需要,改名引入无谓 breaking),skin-hooks 测试删除;
mount e2e 改为断言 --dsh-sidebar-width 布局变量随面板挂载生效。

文档:AGENTS.md §8 重写为「皮肤兼容(令牌驱动)」;设计文档记录决策与
回退。README 与版本号不在本批更新(发布流程统一处理)。

测试:tests/theme.spec.ts(colorAlpha + effectiveTokenValue 阈值);
typecheck/vitest(511)/build/consumer-types/test:mount 全绿。
2026-08-16 16:37:11 +08:00
oy-g fd9544f16b feat: configurable terminal shell with Windows pwsh auto-probe (#95)
* feat: configurable terminal shell with Windows pwsh auto-probe

Windows previously hardcoded powershell.exe (5.1) in defaultShell(), ignoring PowerShell 7 installs. Resolve the shell in priority order: explicit shell config -> DSH_SIDEBAR_SHELL -> PATH/known-dir probe for pwsh.exe -> inbox 5.1 fallback. The same resolved shell feeds both the UI terminal tabs (PtyManager) and the model-facing terminal_* tools (AgentPtyRegistry).

defaultShell() now accepts injectable platform/env/exists options so the Windows chain is unit-testable on the ubuntu CI, which never executes win32 branches. The probe also checks ProgramW6432 for 32-bit Node, and machines without PowerShell 7 keep the 5.1 fallback.

Refs: #46

* fix: trim resolved SHELL and document ProgramW6432 in resolver contract

- POSIX SHELL is validated with trim() but was returned untrimmed; trailing whitespace could leak into the spawned executable path. Return the trimmed value like the explicit/config and Windows/env branches.
- ShellResolutionOptions.env comment now lists ProgramW6432, which the Windows probe prefers over ProgramFiles on 32-bit Node.
- Test pins the SHELL trimming behavior.
2026-08-16 16:34:21 +08:00
時芽ときめ 835d1538e0 Merge pull request #114 from XuYouo/main
fix(ui): 优化侧边卡片设置页图标与布局
2026-08-16 16:24:28 +08:00
ymac 515ed0abd0 feat(editor): add Swift (.swift) syntax highlighting
Map .swift to the built-in swift StreamLanguage from
@codemirror/legacy-modes/mode/swift (no new dependencies).
2026-08-16 04:12:30 +08:00
ymac d7dc609553 feat(editor): add Kotlin (.kt/.kts) syntax highlighting
Map .kt/.kts to the built-in kotlin StreamLanguage from
@codemirror/legacy-modes/mode/clike (no new dependencies).
2026-08-16 04:11:30 +08:00
ymac c9372de69b feat(editor): add C# (.cs) syntax highlighting
Map the .cs extension to the built-in csharp StreamLanguage from
@codemirror/legacy-modes/mode/clike (already a dependency, no new packages),
so .cs files open with syntax highlighting in the sidebar editor.
2026-08-16 04:10:38 +08:00
Atlas Zeng ecebc97800 Merge pull request #24 from log-li/fix/markdown-editor-selection-insert
fix: enable selection-to-conversation popup in the markdown editor
2026-08-15 22:47:21 +08:00
GammaChineYov 1ca4d4ef4d fix: "Show in folder" reveals in the explorer instead of opening a directory in the editor
The open-path interception reroutes EVERY chat-side file open into the
sidebar editor, including the stock "Show in folder" gesture: the
ui-deliverables row passes "." (resolved to "<cwd>/." by the chat view),
so clicking it opened a bogus editor tab for the workspace directory
(host error: '"..." is a directory').

- openpath-intercept: detect the folder-reveal gesture ("." / "./" /
  "<cwd>/.") and route it to a new revealInExplorer dep instead of the editor.
- intercept: revealInExplorer switches to the explorer tab (expanding the
  panel), expands the produced files' parent directories, and highlights /
  scrolls to them; the intercepted produced-files row now also renders its
  own "Show in folder" button when files are hidden.
- produced-files: selectProducedFiles reads the engine Turn data
  (owner.turn.data.get("deliverables")) — the same source ui-deliverables
  uses — with the node replica as fallback. The old code read owner.nodes,
  which the real TurnTailOwnerProps ({turn, seq, openFile}) does not carry,
  so the takeover never claimed the chain.
- state: transient `revealed` set (never persisted) plus a revealPaths
  reducer that expands ancestor directories.
- ExplorerView: highlight + scroll revealed rows; Sidebar/tabs/service
  thread `revealed` through TabComponentProps.
- tests: folder-gesture routing, engine-data selector, wiring seat, zh/en copy.
2026-08-15 17:08:04 +08:00