Commit Graph
16 Commits
Author SHA1 Message Date
HuanLinOTO 31c675a00e feat: show pin icon on home session's pinned tabs too
TabBar's pin icon now shows for both virtual pinned tabs (injected
from other sessions) AND the home session's own pinned terminal tabs
(those with tab.pin !== undefined). Previously only virtual tabs got
the icon — the home tab looked like a regular terminal despite being
pinned.
2026-08-26 22:43:20 +08:00
HuanLinOTO e7c4578791 feat: inline pinned tabs in TabBar (replaces PinnedRail)
M4 rewrite per user feedback: pinned terminals should display in the
Tabs bar itself, not a separate rail. Clicking activates in-place
(cross-session terminal rendering), no session jump.

Changes:
- Delete PinnedRail.tsx + tests/pinned-rail.spec.tsx
- pinned.ts: add createPinnedVirtualTab (virtual id = pinned:<sess>:<id>,
  meta.__pinnedHome = { sessionId, cwd, tabId }), injectPinnedIntoTree
  (appends virtual tabs to first leaf, overrides active), isPinnedVirtualId,
  parsePinnedVirtualId, isPinnedVirtualTab, getPinnedHomeScope
- tab-content-memo.ts: add effectiveTabId to memo key — TabContent passes
  the original tab id (not the virtual id) to the descriptor component so
  TerminalView's WS URL resolves to the home PTY
- Sidebar.tsx: augmented tree (injectPinnedIntoTree), activePinnedTabId
  state, wrappedActions (intercept virtual ids for activate/close/unpin/
  drag/float), renderTab passes home scope + effectiveTabId for pinned tabs,
  pinnedRevision replaces railRevision
- TabBar.tsx: pinned virtual tabs non-draggable, pin icon prefix, stripped
  context menu (Unpin/Close only, no float/close-others/left/right)
- sidebar.module.css: remove rail styles, add .pinnedTab (italic + tertiary)
- Docs: README/README_EN/AGENTS.md/design doc updated for inline approach

157/157 client-side tests pass; typecheck + build clean
2026-08-26 21:40:32 +08:00
HuanLinOTO 5d019d78ac feat: tab context menu pin submenu
M2 of pinned terminals (v0.17.0):
- TabBar: onPinTab prop (optional, hidden when unset for legacy callers)
- Terminal tabs get a 'Pin ▸' submenu (workspace/global) or 'Unpin' row
- UI vs Agent label distinction via isAgentTabId
- Non-terminal tabs get no pin entry (5-item legacy menu preserved)
- WorkbenchActions.pinTab threaded through split-pane LeafView
- Sidebar.onPinTab wires store.reduce(setTabPin) with cwd snapshot
- 12 new i18n keys synced across all 19 language dictionaries
2026-08-26 18:33:02 +08:00
Menghuan1918 f17a1b30ac feat(ui): 自由窗口——tab 拖出侧边栏成为可移动/缩放/置顶的浮动窗口 (v0.16.0)
- 拖出:标签栏 tab 拖到主会话列(HTML5 DnD 检测 + 虚线提示浮层)或右键菜单
  「移动到自由窗口」(右/底面板共用 TabBar),tab 从 pane 移动进 SidebarState.floats
- 窗口(FreeWindow,渲染于 [data-dsh-panel-host] z-42):头部 pointer 拖动
  (rAF 直写 + 松手提交)、拖到 pane 高亮停靠(center 合并)、SE 角缩放、
  点击置顶、头部右键(回到侧边栏/关闭)、X = closeTab 走正常生命周期
- 服务语义:openTab dedupe/id 聚焦浮动 tab = 置顶窗口(不展开面板);
  closeTab/activateTab 兼顾 floats;agent 终端 reconcile 覆盖浮窗;
  features 单调新增 'floatWindows',版本 0.16.0
- 持久化:floats 随会话 localStorage,sanitizeState 宽容校验 + 几何钳制,
  uid 播种覆盖 float 前缀;i18n 三键 19 语言同步
- 关键修复(e2e 发现):portal 覆盖层(头部 Menu)的合成事件沿 React 树
  冒泡回拖拽表面,劫持菜单行点击——拖拽起点加 DOM 同源守卫(jsdom 不复现)
- e2e-mount.sh:缺省 tarball 改取 mtime 最新(旧字典序选择会把冒烟挂到
  过期产物);新增 tests/e2e/float-window.e2e.ts 双用例
2026-08-23 23:44:18 +08:00
Atlas Zeng d3c0f22345 feat(tab-bar): add right-click close context menu on tabs (#331)
Right-clicking a tab now takes over the browser menu and shows four
close operations scoped to the current pane:
- Close
- Close Other Tabs
- Close Tabs to the Left
- Close Tabs to the Right

Items gray out when nothing is eligible (single tab / strip ends).
Bulk closes reuse the per-tab onClose path, so descriptor onClose
callbacks and terminal pty release still run for every close. Labels
are i18n'd (zh/en).
2026-08-22 19:57:34 +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 001e86b1e1 feat: 标签页栏支持鼠标滚轮横向滚动 (#61)
普通鼠标滚轮只产生垂直 delta,overflow-x 的横向滚动容器不会消费它,
事件会冒泡导致页面滚动。给 TabBar 的滚动容器绑定原生非 passive wheel
监听(React 的 onWheel 在根节点是 passive,preventDefault 无效),
在标签条溢出时把 deltaY(含 deltaX、deltaMode 归一化)转为 scrollLeft:

- 未溢出时完全不拦截,页面正常滚动(空 pane 亦然)
- 按住 Shift/Ctrl/Cmd/Alt 时放行,保留浏览器原生横滚/缩放
- 每个 pane 的 TabBar 独立监听,只滚动悬停的标签条

新增 tests/tab-bar-wheel.spec.tsx(jsdom 挂载,7 个用例)。
2026-08-14 22:59:19 +08:00
Menghuan1918 82a69546d2 feat: 公共 API 基座化(v0.12.0,纯增量)— 类型/能力/设置 seam 全面补齐
- 类型生态:service 模块 re-export SidebarTab/SidebarState/SidebarStore/
  SidebarSnapshot/SidebarDiffRef/TabType/SessionScope/SidebarPrefs 等公共
  状态词汇(此前 TS2459 无法命名);context-types 去 Node 依赖(结构镜像
  SidebarHttpRequest/Response/UpgradeSocket/Head),client 可达声明图零
  node:* 引用,外部消费者无 @types/node + skipLibCheck:false 可编译
  (scripts/check-consumer-types.sh 守护)
- 服务能力:version/features 版本与能力探测(单调只增)、getSnapshot/
  subscribeState 状态订阅、updateTab(title/path/meta)、activateTab、
  openFile(路径派生 id 与内置拦截一致)、openTab(seed, scope?) 定向到
  指定 session(store.reduceFor 不扰动 UI)
- TabDescriptor:badge 角标(99+ 封顶,抛错吞掉)、onOpen/onActivate/
  onClose 生命周期回调(service 路径派发,dedupe 聚焦触发 onActivate
  而非 onOpen,抛错不打断流程);SidebarTab.meta JSON 可序列化跨刷新
  持久化(sanitize 宽松保留)
- 设置 seam 开放:PrefsSchema 增 pluginSettings 开放嵌套 map(z.dict),
  settings.pluginToggles(插件自有行,同 switch/text/number 控件)与
  settings.render(自定义面板,含 store/service/prefs/updatePluginSetting/
  close props);viewer 卡片 v0.12.0 起也有齿轮设置弹窗
- FileViewerDescriptor.load 增第三参 AbortSignal(EditorHost 生命周期中止)
- UI 接线:tab 激活/关闭改走 service(跨树激活修复 + 回调触发)、+ 菜单
  传 scope、TabBar badge pill 渲染;active-session scope 走原 reduce 路径
  保证 notify
- 测试 +23:生命周期/定向打开/updateTab/activateTab/openFile/meta 持久化/
  reduceFor/能力常量(与 package.json 锁步)、consumer-types.ts 编译门、
  api-surface.spec.ts(client 图零 node 依赖守护);既有 430 测试全绿
- 文档:AGENTS.md 修 available 三参示例并补全部新 API;external-plugin-
  guide.md 刷新至 v0.12.0(7 tab + 9 viewer、设置 seam 开放、类型导入
  路径建议);README×2 服务化小节同步

未发版 npm(用户决定);分支 feat/plugin-api-v012。
2026-08-14 20:44:43 +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 a6befc8b6b feat: add Office, PDF, and PPTX previews 2026-08-10 16:46:01 +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 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 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 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