Commit Graph
31 Commits
Author SHA1 Message Date
Menghuan1918 302c20109f feat: 内置 HTML 预览器 + 沙箱浏览器 tab(v0.5.0)
- html viewer:.html/.htm 沙箱 iframe 预览(相对资源经 /sidebar/html 路径编码路由加载,CSP sandbox + 信任围栏 + cwd 边界),预览/编辑切换
- browser tab:+ 菜单多开(browser:<n>),地址栏后退/前进/刷新/在浏览器中打开(15px 图标),URL 与标题按会话持久化(patchTab + nextBrowser)
- 安全:iframe 一律不透明源沙箱(无 allow-same-origin/top-navigation、no-referrer、权限策略全禁、禁 srcdoc);地址栏仅 http(s)、拒绝 localhost 等本机地址;界面实时沙箱状态(关闭红色警示)+ 临时解锁/恢复
- 设置:htmlViewerNoSandbox / htmlViewerDefaultUnsafe / browserNoSandbox / browserInterceptLinks 四个声明式二级设置(默认安全)
- 外链拦截:聊天/界面 http(s) 外链默认在侧边栏打开(openTab seed.url),Ctrl/Cmd 点击放行
- 嵌入拒绝面板:browser.probe 路由探测 X-Frame-Options/frame-ancestors,被拒站点显示原因 + 在浏览器中打开/仍然加载
- 设置页可滚动;内置 7 tab + 9 viewer;新增设计文档;268 个测试全绿
2026-08-11 19:28:48 +08:00
Menghuan1918 67ec136e98 feat: 拦截聊天区文件打开 — 聊天里的文件点击默认开进侧边栏编辑器
包装 ctx.workspaces.openPath(聊天区文件打开的唯一漏斗),接管时
经 openSidebarFile 开进 sidebar 编辑器而非系统默认应用;由
interceptOpenPath pref(默认开)与 editor tab 启用态双重门控,
拒接时原样放行,dispose 还原原始方法引用(HMR 安全)。

- prefs-shared/config/prefs: 新增 interceptOpenPath 字段(默认 true)
- openpath-intercept.ts: 零依赖包装器,纯插件层面实现,不改 DSH
- intercept.tsx: registerOpenPathInterception 接线(当前会话 scope)
- SideCardSection: 常规区新增开关卡片(zh/en 文案)
- 测试: 包装器单测 6 个 + 接线集成测试 + pref 解析/设置页断言更新
2026-08-11 16:48:59 +08:00
Menghuan1918 0e20413641 feat(settings): 小卡片响应式网格 + 二级设置原生弹窗
- 清单改为小卡片,置于 repeat(auto-fill, minmax(148px,1fr)) 响应式网格,
  一行自适应排多个、随宽度换行;卡片 = 顶行(图标+标题省略+勾选徽标
  钉在最右端)+ 类型/扩展名描述行(省略号 + title 完整值)
- 声明了 settings.toggles 的卡片右下角渲染齿轮角标(IconSettingsOutline16,
  仅父级启用时显示),点击打开原生 Modal 弹窗:DSH 原生设置行
  (标题/描述 + 品牌强调原生复选框 + hairline 分隔)
- 弹窗行体抽为 FeatureSettingsRows 供直测;Modal 条件挂载
  (常闭挂载在测试 dual-react 分裂下会崩)
- 全部 token 使用 DSH 原生别名(interactive-bg-active/button-primary-fill/
  label-* 等);规格测试改为断言网格卡片与齿轮
- 文档(README/AGENTS.md/设计文档偏差记录 5/6)同步
2026-08-11 14:27:16 +08:00
Menghuan1918 1b0d2085bb feat(settings): 开关改为可点击卡片 — 高亮即启用
- 清单项(侧边栏内容/文件预览)、嵌套相关设置与「新会话默认打开」
  全部从复选框行改为 <button aria-pressed> 卡片:整卡点击切换,
  视觉状态即状态(启用 = 品牌描边 + 交互选中填充 + 勾选徽标;
  禁用 = 中性 hairline + 文字降级),悬停仅在禁用卡上显示
- 嵌套相关设置渲染为更小的缩进卡片(仍仅父级启用时显示);
  宽度输入保持原生设置行(数值非开关)
- 设置导航齿轮为宿主 shell navIcon 硬编码(无插件注入点),
  按用户选择不改宿主、保持现状
- 新增 settingsGeneralTitle 文案;规格测试改为断言 aria-pressed
- 文档(README/AGENTS.md/设计文档偏差记录)同步
2026-08-11 14:19:18 +08:00
Menghuan1918 89ddf8a9e7 feat(settings): 侧边栏设置声明式化 — 注册表驱动的开关清单 + 内置支持插头化
- SidebarPrefs 新增 tabsEnabled / viewersEnabled 开放 map(缺省 = 启用),
  PrefsSchema 用 z.dict 校验,旧文档零迁移
- TabDescriptor/FileViewerDescriptor 新增声明式设置面:viewer 补 title/icon,
  两者补 settings.toggles(子代理声明 autoOpenSubagent、终端声明 agentTerminalTools)
- 服务新增 isTabEnabled/isViewerEnabled;openTab 拒绝禁用类型、matchFileViewer
  跳过禁用 viewer
- SideCardSection 重写为注册表驱动清单:侧边栏内容(图标+类型id+开关+嵌套开关)
  + 文件预览(图标+标题+扩展名+开关),开关持久化 + 乐观提交回滚
- 消费点 gating:+ 菜单过滤、子代理自动展开、agent 终端 reconcile、
  产出文件拦截(editor 关闭时回落默认行)、新会话 explorer 预置
- 内置 viewer 补 6 个手绘 outline 图标;store 快照携带 prefs 使开关变更即时生效
- 测试:service gating / builtins 声明 / parsePrefs map 校验 / schema 断言 /
  新增 SideCardSection renderToString 规格
- 文档:README 设置小节、AGENTS.md 描述符与服务方法、新增设计文档;版本 0.5.0
2026-08-11 14:08:55 +08:00
Menghuan1918 39455ff18e docs+fix: exports 加 ./client/api 别名;catch-all+detect 纯嗅探规则;设计文档偏差记录
- package.json exports 新增 './client/api' 别名指向 service.d.ts(设计文档
  §7 的公共导入路径,api.ts 已被 fetch 助手占用故不换文件名)
- matchFileViewer 补充 sniff-only 规则:exts: [] + detect 的 descriptor 在
  无 head 时不得盲认领(AGENTS.md 的 magic-sniffer 示例原实现会吞掉
  图片/PDF 等真实 viewer 的文件);有 head 时只认领 detect 命中的。
  新增测试断言三种情形(无 head / magic 命中 / magic miss)
- docs/plans/2026-08-11-service-registry-design.md 追加 §17 实施偏差记录:
  dedupeKey+single 语法糖、available 三参、单趟匹配+嗅探规则、EditorHost/
  TextEditor 拆分、fs.read head 字段、导出别名、+ 菜单 disabled 行、
  保留的 store 级操作、内置清单、测试
- AGENTS.md:available 三参、single 语法糖、viewerId、单趟匹配+head 语义、
  8 viewer 内置清单、openTab seed.title 可选、参考实现指向 builtins/ 目录
- README 服务化章节:6 viewer → 8 viewer,标注全注册表驱动
2026-08-11 11:25:37 +08:00
Menghuan1918 1b24bc8ff2 refactor(builtins): 目录重组为 builtins/{tabs,viewers,index},清理死代码与内联样式
- 新 src/client/builtins/ 目录:tabs.tsx(6 个 tab descriptor +
  TERMINAL_LIMIT/uiTerminalCount,terminal 配额知识离开 state.ts)、
  viewers.tsx(8 个 viewer descriptor)、index.ts(registerBuiltins 聚合
  注册 + 幂等 disposer);删除旧 builtins.tsx
- icons.tsx:删除零调用者的 tabTypeIcon 及多余 import(图标已归
  descriptor.icon,映射随 tab 类型一起注册化)
- OrphanedTab:内联 style → sidebar.module.css 新增 .orphanedType 类
- 新增 tests/builtins.spec.ts(6 tab + 8 viewer 注册断言、code catch-all、
  binary-download NUL detect、office 优先级、disposer 幂等)
- 新增 tests/orphaned-tab.spec.tsx(sanitize 保留未注册类型 tab、
  OrphanedTab 占位渲染);tests/browser-globals.ts 供拉入 xterm/
  CodeMirror 的 spec 首个 import(模块求值期需要 self/document/navigator)
- service.spec 补 openDiffTab 与 diff dedupeKey 一致性用例
2026-08-11 11:22:28 +08:00
Menghuan1918 0467101faf feat(editor): 拆分 EditorHost/TextEditor — code/markdown 成为注册 viewer,fs.read 增加 head 字节
- 新 src/client/EditorHost.tsx:'editor' tab 组件。matchFileViewer →
  按 fetchStrategy 分发(纯逻辑在 editor-load.ts)→ 渲染 viewer.component;
  binary 无匹配时渲染共享 BinaryDownload。标题栏保留在 host
- 新 src/client/TextEditor.tsx:code/markdown 的 viewer 组件(CodeMirror +
  保存/脏点/截断横幅 + md 预览/编辑切换),内容由 host 经 fsRead 传入,
  自身不再 fetch;按 viewerId === 'markdown' 分支(不再解析扩展名)
- 新 src/client/editor-load.ts:planFirstMatch / planFsReadOutcome /
  decodeHead 纯函数,策略分发可单测
- 新 src/client/binary-download.tsx:共享下载兜底组件(viewer 注册与
  host fallback 同一 UI)
- host src/index.ts:fs.read 二进制响应增加 head 字段(base64,前 4KB,
  唯一 host 触碰,设计文档 detect 机制所需);客户端 FsBinaryResult 同步
- builtins:注册 8 个 viewer(新增 markdown exts md/markdown、code
  catch-all -100);binary-download 增加 NUL detect + 复用 BinaryDownload
- 删除 src/client/EditorView.tsx(职责一分为二);陈旧注释同步
- 新增 tests/editor-load.spec.ts(12 用例)
2026-08-11 11:16:36 +08:00
Menghuan1918 db0ba36610 fix(service): 注册表核心修正 — seed.title 优先、single 语法糖、available 三参、单趟匹配、dedupe 委托
- service.openTab 的 seed.title 优先于 descriptor.title(修复编辑器 tab 标题
  从文件名变成 t('editor') 的回归);title 变为可选,descriptor.title 兜底
- TabDescriptor 新增 single?: boolean 语法糖(展开为 dedupeKey: () => id),
  dedupeKey 显式给出时优先
- available 签名升级为 (ctx, scope, state) 三参(设计文档 §5.2 的超集),
  Sidebar.buildNewTabOptions 与 terminal 配额检查适配
- matchFileViewer 从三趟全局遍历改为设计文档 §8.2 的单趟 per-descriptor 算法:
  priority 降序,每个 descriptor 先 detect(head 可用时)再 exts,[] 为 catch-all
- applyDedupe 的落位逻辑委托给 state.openTabInActivePane(消除重复实现),
  同时 id 安全网现在也覆盖 service.openTab 路径(同 id 打开聚焦既有 tab)
- state.ts:删除零引用的 SIDEBAR_BUILTIN_TAB_TYPES;TERMINAL_LIMIT 移入
  builtins.tsx(state 不再关心 terminal);openTab reducer 改名 openTabInActivePane
- Sidebar.tsx 删除 TERMINAL_LIMIT/allLeaves 死 import;openDiffTab 补充
  dedupeKey 一致性注释(其按 id 去重与 diff descriptor 的 dedupeKey 同规则)
- tests/service.spec.ts 重写匹配/去重断言并新增 title/single/available 用例
2026-08-11 11:12:29 +08:00
HuanLinOTO e40039ceec refactor: 完全解耦内置 tab — dedupe 归 service,图标归 descriptor
- state.openTab 简化为 '添加到 pane + id dedupe',移除 isSingle/editor-path/diff-id 三分支
  (dedupe 策略归 service.openTab 通过 descriptor.dedupeKey 统一)
- intercept.tsx openSidebarFile 改走 service.openTab(editor descriptor 的 dedupeKey 接管 per-path 去重)
- Sidebar.tsx 两处 subagent 触发(auto-activation + jump-back)改走 service.openTab
- service.openTab seed 加可选 id 字段(editor 需要 path-derived id)
- TabBar.tsx 加 getTabIcon 回调,tab 标签图标查 descriptor.icon 替代 tabTypeIcon
- split-pane.tsx 移除 tabTypeIcon 兜底(buildNewTabOptions 已从 descriptor 填 icon)
- Workbench/NodeView/LeafView 透传 getTabIcon
- tests/unit.spec.ts 更新 openTab 测试(id dedupe 替代 type dedupe)
2026-08-11 01:39:20 +08:00
HuanLinOTO 291158a3ba feat: 服务化改造 — ctx.betterSidebar 注册表(registerTab/registerFileViewer),内置 6 tab + 6 viewer 迁移到 registry,v0.4.0
- 新增 src/client/service.ts: BetterSidebarService 接口 + createBetterSidebarService 工厂
  - registerTab/registerFileViewer 返回 disposer(HMR-safe,挂到 cordis fiber)
  - matchFileViewer 三段匹配: priority desc → detect(内容嗅探) → exts
  - openTab 走 descriptor.dedupeKey 统一去重(替代 isSingle/editor-path/diff-id 三分支)
  - createTab 回调支持自定义 tab 创建(terminal 的 nextTerminal 递增)
- 新增 src/client/builtins.tsx: 6 内置 tab + 6 内置 viewer 自我注册(吃狗粮)
  - tab: editor(hidden) / explorer / git / subagent / terminal / diff(hidden)
  - viewer: image / pdf / docx / xlsx / pptx / binary-download
  - code/markdown 保持 EditorView 内联兜底(matchFileViewer 返回 undefined 时走 fsRead+CodeMirror)
- 新增 src/client/OrphanedTab.tsx: 未注册 type 的占位组件(插件未加载时降级)
- 改造 src/client/index.tsx: ctx.provide('betterSidebar') + registerBuiltins
- 改造 src/client/Sidebar.tsx: TabContent/buildNewTabOptions/onNewTab 查表替代 switch
- 改造 src/client/EditorView.tsx: 预览路由查 matchFileViewer 替代 if/else 链
- 改造 src/client/state.ts: TabType 联合字面量→string,sanitizeNode 接受所有 string type
- 改造 src/context-types.ts: declare module 'cordis' 加 betterSidebar 字段
- 改造 src/index.ts: re-export Context + 服务类型(consumer import type 触发 augmentation)
- package.json: 加 ./client/service exports 子路径,version 0.4.0
- tests/service.spec.ts: 12 个新测试覆盖 register/dispose/matchFileViewer/dedupe/createTab
- tests/unit.spec.ts: sanitizeState 测试更新(未知 type 不再被拒绝,渲染 OrphanedTab)
2026-08-11 00:31:23 +08:00
Menghuan1918 069c754877 feat: Git 面板真正的 diff 显示 + VSCode 式历史与右键高级操作,v0.3.0
- 真正的统一 diff 显示:hunk 头 + 双行号 + 红绿着色,长行换行无横向滚动,untracked 全新增视图
- 点击变更/历史自动打开独立 diff tab(首次在下半栏新增分栏,同变更去重聚焦,临时页签刷新不恢复)
- 历史行:分支徽章 + 作者/相对时间,懒加载分页(20 条/页 + 加载更多)
- 右键菜单:文件行(打开/暂存/放弃更改/复制路径)、历史行(复制哈希/信息/还原/捡取),破坏性操作确认弹窗
- 修复暂存/未暂存分类错误(' M' 被误标为已暂存导致 diff 恒空)
- 修复 git 相对路径解析(会话在仓库子目录时 diff/discard/show/read 可用)
- host 新增 git.commit-diff / git.discard / git.revert / git.cherry-pick 路由与 log 分页
2026-08-10 22:29:24 +08:00
Menghuan1918 8edfbfde44 fix: 修复 agent 终端工具审查问题(terminal_list 输出契约、跨会话归属、UTF-8 截断边界等),工具注入改为设置面板开关(默认关闭) 2026-08-10 21:43:58 +08:00
HuanLinOTO 6f62d19905 feat: 为 agent 暴露 8 个终端工具(terminal_create/list/send/read/wait_for/resize/signal/close),侧边栏自动同步 agent 终端 tab,v0.2.0 2026-08-10 19:08:58 +08:00
Menghuan1918 61b18cbefe feat: Subagent 拓扑页面(主代理根节点 + 分层树 + 实时工具调用 + 自动激活设置),v0.1.9 2026-08-10 17:24:42 +08:00
HuanLinOTO eb5059574e fix: route images before binary detection 2026-08-10 16:54:13 +08:00
HuanLinOTO a6befc8b6b feat: add Office, PDF, and PPTX previews 2026-08-10 16:46:01 +08:00
Menghuan1918 a1286191d6 feat: plugin-registry 标准发布面(dsh.plugin.json + 双 client bundle + registry 打包),v0.1.2
- dsh.plugin.json 原生清单:id dsh-external/dsh-better-sidebar,version 0.1.2,
  main = lib/index.js,client.main = lib/client-registry.js,contributes 空声明
- tsdown client 构建参数化:lib/client.js(官方通道,id = 包名)+ lib/client-registry.js
  (registry 通道,id = 清单 id),同源不漂移
- scripts/package-registry.mjs 组装 registry/ 安装根(gitignore,不入库)
- tests/manifest-consistency.spec.ts:id 两段校验 / version 同步 / 入口存在 /
  双 bundle id 不互换 / contributes 空
- README:新增「通过 plugin-registry 安装(标准)」章节 + 保留官方 profile 安装
  方案 + 通道互斥说明;架构表补 manifest 与双 client 行
2026-08-09 23:49:58 +08:00
Menghuan1918 9b00135de4 feat: DSH-native flat chrome + Side card settings (plugin-owned settings wire)
- Remove every box-shadow; panel uses the native sidebar fill surface
  (--dsw-specific-sidebar-fill) with a hairline border, the collapse
  control becomes the native 36px circular rail button, the active tab
  uses the native interactive-bg-active fill, and rows/typography follow
  the native 34px/14px session-row recipe.
- New 'Side card' settings section in the DSH Settings shell: open-by-
  default for new conversations (boolean) and default width share as a
  percent of the window (20-60). New sessions seed from these prefs;
  existing per-session layouts stay untouched.
- The settings namespace is read/written through the plugin's own fenced
  /sidebar routes calling the settings seam in-process: the DSH settings
  RPC domain only serves allowlisted namespaces to configuration clients,
  so third-party namespaces cannot use connection.api.settings (reads are
  silently filtered, writes refused with 'not exposed'). The seam still
  persists to the user settings document with revision-guarded writes.
2026-08-09 23:49:58 +08:00
Menghuan1918 81ab0c5312 fix: cross-platform path handling and tests
- fs-tree joins listed paths with the platform separator (Windows rows
  previously leaked mixed separators)
- media route containment check goes through isWithin(): separator-
  tolerant and case-insensitive on win32, so case-mismatched Windows
  paths are not misclassified
- relativeTo compares case-insensitively (Windows / case-insensitive
  macOS volumes) while keeping the caller's own casing in the output
- tests: platform-conditional path assertions, os.tmpdir() instead of
  /tmp, isWithin/relativeTo/resolveSidebarPath win32 coverage
2026-08-09 22:07:03 +08:00
Menghuan1918 e2ee427ea3 feat: per-session store + configurable host limits; fix divider drag runaway
- state: side-store per conversation, injected as a prop instead of a module singleton
- host: schemastery Config (read/media/list/terminal limits) resolved from cordis config
- shape: types/exports/peerDependencies for the web profile + plugin-shape spec
- fix: split divider emits incremental deltas so drags track the cursor 1:1
- docs: README refresh
2026-08-09 22:07:03 +08:00
Menghuan1918 fec53cef0d fix: split panes duplicate when a file opens — stale uid collisions with persisted ids
The uid counter is module-global and resets on every reload, while the
persisted split tree keeps its old ids (pane:1, split:1, ...). A split
created AFTER a reload minted a fresh "pane:1" beside the persisted
"pane:1"; mapLeaf then visited BOTH leaves, so every open (explorer click,
produced-files chip) landed the tab in both panes of a top-bottom split.

- loadState seeds the counter past the raw persisted ids before sanitize
- setSession re-seeds on cache hits (another session's smaller max must
  not leave the counter below this session's ids)
- sanitize re-ids stale duplicate pane/split ids (heals states already
  corrupted) and follows the activePane rename
- openTab falls back to the first pane when the active pane is gone
  instead of silently dropping the open

Unit tests cover the re-id heal and the stale-activePane fallback;
verified end-to-end in headless Chrome: a state with two "pane:1" leaves
loads healed and a file click lands in exactly one pane.
2026-08-09 00:14:27 +08:00
Menghuan1918 6e36a99c88 feat: explorer copy-path & @-reference actions; icons on tabs and + menu
- Tabs, the + menu, and empty-pane cards carry per-type icons (folder /
  branch / terminal / code file); a terminal glyph joins the icon set
- Hovering an explorer row reveals an @文件 button at the far right that
  appends @<relative path> to the composer draft (session-scoped input
  service, space-separated append)
- Right-clicking a row opens a context menu: copy relative / absolute
  path (writeClipboard + transient in-row "copied" label)
- Rows become div role=button (Enter/Space) to host the inner button;
  relativeTo helper unit-tested
2026-08-08 23:46:14 +08:00
Menghuan1918 d1cdca3324 terminal: keep the pty alive while its tab is open, whatever the view does
The close frame was sent on every unmount, so switching conversations (or
refreshing) killed the shell even though the tab was still open; coming
back spawned a fresh terminal. Now the unmount close frame only fires when
the tab is truly gone (checked against the per-session store state), so a
bare unmount hands the process to the host's reconnect grace and the next
connect reattaches the SAME shell with its transcript. Closing a tab
releases the quota immediately: the socket-open path keeps the close
frame, and a new pty.close API covers the socket-down (reconnect loop)
case that previously left a 30s zombie holding the quota.

The host also records the spawn cwd and respawns when a reconnect carries
a different authoritative cwd — the page-load hydrate race can attach the
real session cwd after the first connect fell back to the process cwd, and
a shell in the wrong directory must not linger.
2026-08-08 17:24:08 +08:00
Menghuan1918 97e1f60e47 ui: preview/edit toggle for markdown, empty-pane open cards, + flush to the rightmost tab
- markdown tabs open in rendered preview with a 预览/编辑 segmented toggle;
  edit mode uses the CodeMirror source editor (markdown highlighting), the
  preview renders the live unsaved draft, and the editor stays mounted while
  previewing so edits survive the toggle.
- an empty pane shows openable-type cards (explorer / git / terminal, the
  same options as the + menu, terminal dimmed at the cap) instead of the
  bare 'this pane is empty' message; clicking a card opens the tab.
- the + button sits immediately right of the last tab (sticky at the
  scrollport's right edge when the tab row overflows, so it stays
  reachable).
2026-08-08 15:16:09 +08:00
Menghuan1918 47ebffcf9b fix: session-cwd attach race, terminal failure surfacing, white-screen hardening; CodeMirror editor
- cwd resolution falls back (attached header → client summary cwd → process
  cwd) across the JSON API, media route and terminal upgrade, so explorer/
  git/terminal work from first paint instead of 'session ... has no working
  directory' while the web client hydrates the session (refresh-only fix).
- terminal client threads the summary cwd into the WS URL and stops the
  reconnect loop after three unreasoned failures, showing the close code
  with a manual retry; a 1011+reason refusal already showed the reason.
- white screen: persisted state is structurally sanitized on load (malformed
  shapes fall back to default) and the restored width is clamped to the
  viewport; the layout margin is capped at the viewport; the client entry
  wraps every lifecycle step in try/catch and pins a visible diagnostic bar
  on load/mount/interception failure instead of failing silently.
- editor: textarea replaced with CodeMirror 6 — line wrapping, per-extension
  syntax highlighting (js/ts/jsx/tsx, json, python, html, css, xml, yaml,
  sql, java, c/c++, rust, go, php, shell, toml, nginx, dockerfile,
  properties), one-dark theme, dirty dot + Ctrl/Cmd+S save kept.
- tests: +9 (cwd fallback chain over the API route, state sanitization,
  language mapping); 36 pass, typecheck + build + vm materialize clean.
2026-08-08 15:12:56 +08:00
Menghuan1918 8950e2ae69 fix: zombie terminals eating the quota; tab drop double-firing into two panes
- The per-session terminal quota counted exited zombie handles (shells
  closed or dropped on hosts without the close-frame protocol), so enough
  abandoned terminals permanently blocked new ones — the 'disconnected,
  reconnecting' loop. open() now sweeps the session's exited handles before
  the quota check, and the running instance's leftovers disappear on the
  next restart (disposeAll).
- Dragging a tab onto the strip's background fired the drop TWICE: the
  TabBar handled it (merge into this pane) and the event bubbled to the
  pane root, which ran its edge-zone handler again — producing two splits /
  a duplicated instance (up+down). The strip now stops propagation for
  drag-over and drop.
- Tests: +1 real-pty smoke (exited handle swept, new tab spawns under a
  quota of 1).
2026-08-08 14:54:40 +08:00
Menghuan1918 0621504e1b fix: terminal quota leak and dead-terminal reuse; error boundary against blank panel
- Terminals were never destroyed when their tab closed (socket drop only
  detached listeners), so the per-session quota (3) filled up and new
  terminals failed to mount with 'terminal limit reached'. Now the client
  sends a {type:'close'} control frame on tab teardown and the host releases
  the pty immediately; a bare socket drop (refresh/switch) keeps the process
  for a 30s grace period, and a reconnect within it cancels the pending
  close. open() also respawns a process whose shell already exited instead
  of reusing a dead handle (input sink).
- The sidebar portal renders inside an error boundary: a render failure now
  shows a dismissible error strip with the message (console.error with the
  component stack) instead of a silent blank panel.
- Tests: +2 real-pty smoke specs (close releases quota + respawn after
  exit; reconnect cancels pending close).
2026-08-08 12:31:22 +08:00
Menghuan1918 c7a7c7e5af terminal: fix posix_spawnp failure (spawn-helper exec bit), half-viewport default, fullscreen toggle, middle-click close
- Root cause of the endless 'disconnected, reconnecting' banner: pnpm
  strips the executable bit from node-pty's prebuilt spawn-helper, so every
  spawn failed with posix_spawnp failed and the client retried forever.
  ensureSpawnHelper() restores 0755 at plugin activation (mirrors
  dsh-pty-local's postinstall; works for link installs too). Verified
  against the live GUI: terminal now connects and echoes.
- TerminalView stops auto-reconnecting on a server refusal (close 1011 with
  a reason): shows the reason plus a manual retry instead of spinning.
- defaultShell() also guards an empty SHELL.
- New sessions open at half the viewport width; width clamping now allows
  filling the window, and a header fullscreen button expands the sidebar to
  the full viewport (toggle restores half).
- Middle-click on a tab closes it (auxclick button 1, autoscroll
  suppressed).
- Tests: +2 (shell fallback, spawn-helper exec bit idempotent).
2026-08-08 11:28:43 +08:00
Menghuan1918 58b4bd71ba sidebar: VSCode-style drag-to-edge splitting, collapse instead of close, theme-compliant tokens
- Split panes by dragging a tab to a pane edge (left/right/up/down zones
  with a drop overlay) or into the center to merge; the toolbar split
  buttons are gone (split-pane.tsx, state.ts moveTabToEdge/insertLeafAt).
- The header close button is now a collapse button; the expand button on
  the right edge stays visible whenever the panel is collapsed, so the
  sidebar can always be reopened (Sidebar.tsx, locales collapse/expand).
- Styles now follow the DSH web-styling rules exactly: --dsw-alias-*
  semantic colors (state-error/warn tokens), --dsw-font-* typography roles
  (size + line-height paired), --dsw-shadow-lv* elevation, no color
  literals or static palette values (sidebar.module.css).
- Tests: +3 for moveTabToEdge (edge split, center merge, self reorder).
2026-08-08 10:35:47 +08:00
Menghuan1918 63e2988ef1 dsh-better-sidebar: VSCode-like right sidebar plugin for the DSH web GUI
Host half (src/index.ts): /sidebar/api JSON API (session cwd, fs tree/read/
write, git status/diff/stage/commit/branch/log/show), /sidebar/file media
route, /sidebar/ws/terminal WebSocket over node-pty; routes share the /api
trust fence (Host-header loopback or connection.trustedHosts).

Client half (src/client): portal-mounted right panel with a per-session
split-pane workbench (recursive row/col splits, draggable dividers, tab
drag-move), tab bar with max-width 160px + plus-menu (explorer/git/terminal
multi-open rules), lazy file explorer rooted at the session cwd, editable
text editor (dirty dot, Ctrl/Cmd+S), image + Markdown preview, xterm
terminal with reconnect, and a VSCode-style git panel. Per-session state
persists to localStorage (dsh-sidebar:v1:<sessionId>). Intercepts the chat
turn-tail produced-files row (priority -1 chain entry) to open files in the
sidebar. UI styled with the dsh --dsw-* theme tokens and ui-primitives.

Build: tsdown emits the host lib plus the __ModuleLoader__ client bundle
(externals = module table words; xterm/css inlined). Tests: 19 unit + smoke
specs (pure state/tree/git parsers + real git/fs integration).
2026-08-08 01:02:24 +08:00