diff --git a/README.md b/README.md
index b7f1164..ddba404 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,8 @@ control panel — **isolated per conversation session**.
- Editor: 文本编辑(脏点 + Ctrl/Cmd+S 保存)、图片查看、Markdown 预览
- Terminal: xterm.js + node-pty(每会话最多 3 个,进程跨刷新存活)
- Git: status / diff / stage / commit / branch / history(基础集)
-- 分栏工作台: 上下左右递归分栏、分隔线拖拽、Tab 拖拽移动、面板宽度拖拽
+- 分栏工作台: VSCode 式拖拽分栏(拖 Tab 到分栏边缘即左右/上下拆分,拖到中间合并)、
+ 分隔线拖拽、面板宽度拖拽;折叠后右缘展开按钮常驻
- Tab 栏: 最大宽度 160px、横向滚动、`+` 菜单新建 Tab
- 拦截: 对话"产出文件"行点击改为在侧边栏打开
- 样式完全复用 `--dsw-*` 主题变量与 `@deepseek-ai/dsh-client-ui-primitives`
diff --git a/src/client/Sidebar.tsx b/src/client/Sidebar.tsx
index 213d11e..2a9c3eb 100644
--- a/src/client/Sidebar.tsx
+++ b/src/client/Sidebar.tsx
@@ -12,12 +12,12 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useSyncExternalStore } from 'react'
import clsx from 'clsx'
-import { IconCloseOutline16, IconPanelLeftOutline16, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'
+import { IconChevronRightOutline14, IconPanelLeftOutline16, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'
import type { Context } from '../context-types.ts'
import {
- allLeaves, closeTab, leafWithTab, mapLeaf, moveTab, openTab, resizeSplit,
- sidebarStore, splitPane, setWidth, toggleExpanded, togglePanel,
- TERMINAL_LIMIT, type SidebarState, type SidebarTab, type SplitNode,
+ allLeaves, closeTab, leafWithTab, mapLeaf, moveTab, moveTabToEdge, openTab,
+ resizeSplit, sidebarStore, setWidth, toggleExpanded, togglePanel,
+ TERMINAL_LIMIT, type DropZone, type SidebarState, type SidebarTab, type SplitNode,
} from './state.ts'
import { Workbench, type WorkbenchActions } from './split-pane.tsx'
import type { NewTabOption } from './TabBar.tsx'
@@ -113,15 +113,15 @@ export function Sidebar(props: { ctx: Context }) {
}))
},
focusPane: (paneId) => { sidebarStore.reduce(s => ({ ...s, activePane: paneId })) },
- splitPane: (dir) => { sidebarStore.reduce(s => splitPane(s, dir)) },
- moveTab: (payload: TabDragPayload, toPane: string, before: string | null) => {
+ moveTabToEdge: (payload: TabDragPayload, toPane: string, zone: DropZone) => {
+ sidebarStore.reduce(s => moveTabToEdge(s, payload.paneId, payload.tabId, toPane, zone))
+ },
+ moveTabBefore: (payload: TabDragPayload, toPane: string, beforeTabId: string) => {
sidebarStore.reduce((s) => {
let index = -1
- if (before !== null) {
- const source = leafWithTab(s.splits, before)
- if (source !== undefined && source.id === toPane) {
- index = source.tabs.findIndex(tab => tab.id === before)
- }
+ const source = leafWithTab(s.splits, beforeTabId)
+ if (source !== undefined && source.id === toPane) {
+ index = source.tabs.findIndex(tab => tab.id === beforeTabId)
}
return moveTab(s, payload.paneId, payload.tabId, toPane, index)
})
@@ -181,11 +181,11 @@ export function Sidebar(props: { ctx: Context }) {
<>
{!state.panelOpen && (
-
+
diff --git a/src/client/TabBar.tsx b/src/client/TabBar.tsx
index 4009531..a5c52b6 100644
--- a/src/client/TabBar.tsx
+++ b/src/client/TabBar.tsx
@@ -8,11 +8,10 @@
import { useState, type ReactNode } from 'react'
import clsx from 'clsx'
import {
- IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14,
- IconChevronUpOutline14, IconCloseFill14, IconPlusOutline16, Menu,
+ IconCloseFill14, IconPlusOutline16, Menu,
} from '@deepseek-ai/dsh-client-ui-primitives'
import type { SidebarTab } from './state.ts'
-import { t, type CopyKey } from './locales.ts'
+import { t } from './locales.ts'
import css from './sidebar.module.css'
/** One + menu option. */
@@ -44,27 +43,19 @@ export function parseDrag(raw: string): TabDragPayload | null {
}
}
-const SPLIT_DIRS: Array<{ dir: 'left' | 'right' | 'up' | 'down'; label: CopyKey; icon: (p: { size?: number }) => ReactNode }> = [
- { dir: 'left', label: 'splitLeft', icon: IconChevronLeftOutline14 },
- { dir: 'right', label: 'splitRight', icon: IconChevronRightOutline14 },
- { dir: 'up', label: 'splitUp', icon: IconChevronUpOutline14 },
- { dir: 'down', label: 'splitDown', icon: IconChevronDownOutline14 },
-]
-
export function TabBar(props: {
paneId: string
tabs: SidebarTab[]
active: string | null
onActivate: (tabId: string) => void
onClose: (tabId: string) => void
- onSplit: (dir: 'row' | 'col') => void
onNewTab: (optionId: string) => void
newTabOptions: NewTabOption[]
/** Drop of a tab from any pane: (payload, insertBeforeTabId | null). */
onDropTab: (payload: TabDragPayload, before: string | null) => void
}) {
const {
- paneId, tabs, active, onActivate, onClose, onSplit, onNewTab, newTabOptions, onDropTab,
+ paneId, tabs, active, onActivate, onClose, onNewTab, newTabOptions, onDropTab,
} = props
const [menuOpen, setMenuOpen] = useState(false)
const [dragOver, setDragOver] = useState(false)
@@ -122,18 +113,6 @@ export function TabBar(props: {
))}
- {SPLIT_DIRS.map(({ dir, label, icon: Icon }) => (
-
{ onSplit(dir === 'left' || dir === 'right' ? 'row' : 'col') }}
- >
-
-
- ))}