dsh-vscode-mode 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
4
+ * 作者 ddj 2026-08-26
5
+ */
6
+ import React from 'react'
7
+ import { FileExplorer } from './FileExplorer.js'
8
+ import type { SidebarPanelDef, SidebarCtx } from '../types.js'
9
+
10
+ /**
11
+ * 构造文件管理面板定义。
12
+ * @author ddj 2026年08月26号
13
+ * @returns 面板定义(含活动栏徽标 = 差异文件总数)
14
+ */
15
+ export function createFilePanel(): SidebarPanelDef {
16
+ return {
17
+ id: 'explorer',
18
+ title: '文件管理',
19
+ icon: '📁',
20
+ order: 10,
21
+ badge: (ctx: SidebarCtx) => (ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null),
22
+ render: (ctx: SidebarCtx) => React.createElement(FileExplorer, { ctx }),
23
+ }
24
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * dsh-vscode-mode client — 侧边栏面板注册表。
3
+ * 镜像 fileOpeners.ts 的注册表模式:create + register(返回注销) + list + subscribe,
4
+ * 由 client/index.ts `ctx.provide('edrvSidebarPanels', registry)` 对外暴露(第三方可注册)。
5
+ * 作者 ddj 2026-08-26
6
+ */
7
+ import type { SidebarPanelDef } from './types.js'
8
+
9
+ export interface SidebarPanelRegistry {
10
+ register(panel: SidebarPanelDef): () => void
11
+ list(): readonly SidebarPanelDef[]
12
+ subscribe(listener: () => void): () => void
13
+ get(id: string): SidebarPanelDef | undefined
14
+ }
15
+
16
+ /** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
17
+ function panelRegister(entries: Map<string, SidebarPanelDef>, notify: () => void, panel: SidebarPanelDef): void {
18
+ if (!panel.id || typeof panel.render !== 'function') throw new TypeError('侧边栏面板必须提供 id 和 render')
19
+ entries.set(panel.id, panel)
20
+ notify()
21
+ }
22
+
23
+ /**
24
+ * 创建生命周期独立的面板注册表。
25
+ * @author ddj 2026年08月26号
26
+ * @returns 面板注册表
27
+ */
28
+ export function createSidebarPanelRegistry(): SidebarPanelRegistry {
29
+ const entries = new Map<string, SidebarPanelDef>()
30
+ const listeners = new Set<() => void>()
31
+ const notify = (): void => {
32
+ for (const listener of listeners) listener()
33
+ }
34
+ const list = (): readonly SidebarPanelDef[] =>
35
+ [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
36
+ return {
37
+ register(panel: SidebarPanelDef): () => void {
38
+ panelRegister(entries, notify, panel)
39
+ return () => {
40
+ if (entries.get(panel.id) !== panel) return
41
+ entries.delete(panel.id)
42
+ notify()
43
+ }
44
+ },
45
+ list,
46
+ subscribe(listener: () => void): () => void {
47
+ listeners.add(listener)
48
+ return () => listeners.delete(listener)
49
+ },
50
+ get: (id: string) => entries.get(id),
51
+ }
52
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * dsh-vscode-mode client — 侧边栏面板契约(类型 + 上下文)。
3
+ * 面板 = 活动栏一项 + 面板区一处渲染;新增面板只需注册一条 SidebarPanelDef。
4
+ * 作者 ddj 2026-08-26
5
+ */
6
+ import type { OutlineSourceRegistry } from '../outline/types.js'
7
+
8
+ /** 面板可用的共享上下文(由 SidebarView 从 EditorView 注入,面板不直碰其内部)。 */
9
+ export interface SidebarCtx {
10
+ sessionId?: string
11
+ openFile: (path: string) => void
12
+ activePath: string | null
13
+ /** 文件路径 → 待处理差异数(>0 才收录)。 */
14
+ pendingByPath: Record<string, number>
15
+ /** 按文件分组的差异摘要(records 状态派生)。 */
16
+ sum: { totalFiles: number; files: Array<{ path: string; pending: number }> }
17
+ /** 触发差异记录刷新(保存/决策后由 EditorView 统一 emitRefresh)。 */
18
+ refreshRecords: () => void
19
+ /** 取当前 Monaco 编辑器实例(可能为 null;大纲面板读 model/跳转用)。 */
20
+ editor?: () => unknown | null
21
+ /** 大纲源注册表(公开 provide 为 edrvOutlineSources;大纲面板解析符号用)。 */
22
+ outlineSources?: OutlineSourceRegistry
23
+ }
24
+
25
+ /** 单个侧边栏面板定义。 */
26
+ export interface SidebarPanelDef {
27
+ id: string
28
+ title: string
29
+ /** 活动栏图标(文本/SVG 均可,横向居中显示)。 */
30
+ icon: string
31
+ order?: number
32
+ /** 活动栏徽标:返回数字/null;缺省不显示。 */
33
+ badge?: (ctx: SidebarCtx) => number | null
34
+ /** 面板区渲染(ctx 每次渲染注入最新快照)。 */
35
+ render: (ctx: SidebarCtx) => unknown
36
+ }
@@ -1,7 +1,7 @@
1
1
  /* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */
2
2
  /* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):
3
3
  在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */
4
- [data-edrv-view] { --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }
4
+ [data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }
5
5
 
6
6
  [data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }
7
7
  [data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
@@ -15,6 +15,8 @@
15
15
  [data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }
16
16
  [data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
17
17
  [data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }
18
+ [data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }
19
+ [data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }
18
20
  [data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }
19
21
  [data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }
20
22
  [data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }
@@ -43,11 +45,12 @@
43
45
  [data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }
44
46
  [data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }
45
47
  [data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
46
- [data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }
47
- [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }
48
+ [data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
49
+ [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }
48
50
  [data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }
49
- [data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 16px rgba(31,41,51,.12); overflow: visible; display: flex; flex-direction: column; }
50
- [data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; flex-wrap: nowrap; }
51
+ [data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }
52
+ [data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }
53
+ [data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }
51
54
  [data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }
52
55
  [data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }
53
56
  [data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -105,3 +108,125 @@
105
108
  [data-edrv-view] .edrv-progress-value { transition: none; }
106
109
  }
107
110
 
111
+ /* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */
112
+ [data-edrv-diff-dock-shell] {
113
+ box-sizing: border-box;
114
+ width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));
115
+ max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));
116
+ margin: 0 auto;
117
+ padding: 0;
118
+ flex: none;
119
+ border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);
120
+ border-radius: 12px;
121
+ background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));
122
+ box-shadow: none;
123
+ overflow: hidden;
124
+ }
125
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock {
126
+ box-sizing: border-box;
127
+ position: relative;
128
+ left: auto;
129
+ right: auto;
130
+ bottom: auto;
131
+ z-index: auto;
132
+ width: 100%;
133
+ max-width: none;
134
+ margin: 0;
135
+ display: flex;
136
+ flex-direction: column;
137
+ border: none;
138
+ border-radius: 0;
139
+ background: transparent;
140
+ box-shadow: none;
141
+ overflow: visible;
142
+ }
143
+ [data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }
144
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }
145
+ [data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }
146
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
147
+ [data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }
148
+ [data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }
149
+ [data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }
150
+ [data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }
151
+ [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }
152
+ [data-edrv-diff-dock-shell] .edrv-diffbar-nav,
153
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files,
154
+ [data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }
155
+ [data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }
156
+ [data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }
157
+ [data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }
158
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }
159
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }
160
+ [data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }
161
+ [data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }
162
+ [data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }
163
+ [data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }
164
+ [data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }
165
+ [data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }
166
+ [data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }
167
+ [data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }
168
+ [data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }
169
+ [data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }
170
+ [data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }
171
+ [data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }
172
+ [data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }
173
+ [data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }
174
+ [data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }
175
+ [data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }
176
+ [data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }
177
+ [data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }
178
+ [data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
179
+ [data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }
180
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }
181
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
182
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }
183
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }
184
+ [data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }
185
+ @media (max-width: 620px) {
186
+ [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }
187
+ [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }
188
+ }
189
+
190
+ /* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */
191
+ [data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }
192
+ [data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }
193
+ [data-edrv-view] .edrv-main-col > .edrv-pathbar,
194
+ [data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }
195
+ [data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
196
+ [data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 0; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }
197
+ [data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }
198
+ [data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }
199
+ [data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
200
+ [data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }
201
+ [data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }
202
+ [data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }
203
+ [data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }
204
+ [data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
205
+ [data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }
206
+ [data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }
207
+ [data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
208
+ [data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }
209
+ [data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
210
+ [data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }
211
+ [data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }
212
+ [data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }
213
+ [data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }
214
+ [data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }
215
+ [data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
216
+ [data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
217
+ [data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }
218
+ [data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }
219
+ [data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }
220
+ /* 大纲面板:kind 字形 / detail / 行号 */
221
+ [data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }
222
+ [data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }
223
+ [data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }
224
+ [data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }
225
+ [data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }
226
+ [data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }
227
+ [data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }
228
+ [data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }
229
+ [data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }
230
+ [data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }
231
+ [data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }
232
+
@@ -0,0 +1,84 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — 会话级差异 dock。
4
+ * conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
5
+ * 作者 ddj 2026-08-26
6
+ */
7
+ import React from 'react'
8
+ import { rpc } from '../rpc.js'
9
+ import { openDiffView } from '../events.js'
10
+ import { summarize } from '../state/records.js'
11
+ import { nextDiffPath } from '../diffDock.js'
12
+ import { readDiffDock, subscribeDiffDock } from '../diffDockStore.js'
13
+ import { DiffBox } from './DiffBox.js'
14
+
15
+ const nextIndexBySession = new Map()
16
+
17
+ /**
18
+ * 对话输入框上方的唯一差异 dock。
19
+ * @author ddj 2026年08月26号
20
+ * @param props DSH 注入的会话属性
21
+ * @returns 差异 dock React 元素或 null
22
+ */
23
+ export function ConversationDiffDock(props) {
24
+ const sessionId = props?.sessionId
25
+ const [summary, setSummary] = React.useState(null)
26
+ const [, setStoreRevision] = React.useState(0)
27
+ const seq = React.useRef(0)
28
+
29
+ const load = React.useCallback(() => {
30
+ if (!sessionId) return
31
+ const current = ++seq.current
32
+ rpc('edrv.list', { sessionId }).then((res) => {
33
+ if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return
34
+ setSummary(summarize(res.records))
35
+ }).catch(() => {})
36
+ }, [sessionId])
37
+
38
+ React.useEffect(() => {
39
+ load()
40
+ const timer = setInterval(load, 5000)
41
+ const onRefresh = () => load()
42
+ window.addEventListener('edrv:refresh', onRefresh)
43
+ return () => {
44
+ clearInterval(timer)
45
+ window.removeEventListener('edrv:refresh', onRefresh)
46
+ }
47
+ }, [load])
48
+
49
+ React.useEffect(() => {
50
+ if (!sessionId) return undefined
51
+ const sync = () => setStoreRevision((value) => value + 1)
52
+ sync()
53
+ return subscribeDiffDock(sessionId, sync)
54
+ }, [sessionId])
55
+
56
+ const renderDock = (content, key) => React.createElement('div', {
57
+ key,
58
+ className: 'edrv-diff-dock-shell',
59
+ 'data-edrv-diff-dock-shell': '1',
60
+ }, content)
61
+
62
+ const editorSnapshot = readDiffDock(sessionId)
63
+ if (editorSnapshot) {
64
+ if (editorSnapshot.mode === 'editor-empty' && !editorSnapshot.fileTotal) return null
65
+ return renderDock(React.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId)
66
+ }
67
+
68
+ if (!sessionId || !summary?.pendingFiles?.length) return null
69
+
70
+ const paths = summary.pendingFiles.map((file) => file.path)
71
+ const click = () => {
72
+ const current = nextIndexBySession.get(sessionId) ?? 0
73
+ const next = nextDiffPath(paths, current)
74
+ nextIndexBySession.set(sessionId, next.index)
75
+ if (next.path) openDiffView(next.path)
76
+ }
77
+
78
+ return renderDock(React.createElement(DiffBox, {
79
+ mode: 'chat',
80
+ dock: true,
81
+ fileTotal: paths.length,
82
+ onOpenNextFile: click,
83
+ }), sessionId || 'chat')
84
+ }
@@ -6,24 +6,63 @@
6
6
  */
7
7
  import React from 'react'
8
8
  import { callIdAttr } from '../state/records.js'
9
+ import { diffDockText } from '../diffDock.js'
9
10
  import { badgeOf } from './shared.js'
10
11
 
12
+ /**
13
+ * 折叠控制使用任务栏同尺寸的 chevron 轮廓,避免依赖未声明的运行时图标包。
14
+ * @author ddj 2026年08月25号
15
+ * @param open 是否已展开
16
+ * @returns 14×14 的装饰性 chevron SVG
17
+ */
18
+ function chevronOf(open) {
19
+ const path = open ? 'M3.25 5.25 7 9l3.75-3.75' : 'M3.25 8.75 7 5l3.75 3.75'
20
+ return React.createElement('svg', {
21
+ width: 14,
22
+ height: 14,
23
+ viewBox: '0 0 14 14',
24
+ fill: 'none',
25
+ 'aria-hidden': true,
26
+ focusable: false,
27
+ }, React.createElement('path', {
28
+ d: path,
29
+ stroke: 'currentColor',
30
+ strokeWidth: 1.2,
31
+ strokeLinecap: 'round',
32
+ strokeLinejoin: 'round',
33
+ }))
34
+ }
35
+
11
36
  /**
12
37
  * 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
13
38
  * (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
14
39
  */
15
40
  export function DiffBox(props) {
16
41
  const {
17
- pendingRegions, staleRegions, onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles,
18
- allPendingCount, onRollback, onJump, otherFiles, onOpenOther, onOpenLauncher, onRefresh, activePath,
19
- diffIdx, diffTotal, fileIdx, fileTotal, onPrevDiff, onNextDiff, onPrevFile, onNextFile,
42
+ pendingRegions = [], staleRegions = [], onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles,
43
+ allPendingCount = 0, onRollback, onJump, otherFiles = [], onOpenOther, onOpenLauncher, onRefresh, activePath,
44
+ diffIdx = 0, diffTotal = 0, fileIdx = 0, fileTotal = 0, onPrevDiff, onNextDiff, onPrevFile, onNextFile,
45
+ mode = 'editor', onOpenNextFile, dock = false,
20
46
  } = props
21
47
  const [expanded, setExpanded] = React.useState(false)
22
- const [menuOpen, setMenuOpen] = React.useState(false)
48
+ const [detailsOpen, setDetailsOpen] = React.useState(false)
49
+ const detailsId = React.useId()
23
50
  const canAct = pendingRegions.length > 0
24
51
 
25
52
  const base = String(activePath || '').split(/[\\/]/).pop() || ''
26
53
 
54
+ if (mode === 'chat' || mode === 'editor-empty') {
55
+ return React.createElement('div', { className: 'edrv-diffbar edrv-diffbar-dock', 'data-edrv-diffbox': '1', 'data-edrv-diff-mode': mode },
56
+ React.createElement('div', { className: 'edrv-diffbar-row edrv-diffbar-dock-row' },
57
+ React.createElement('button', {
58
+ type: 'button',
59
+ className: 'edrv-diffbar-dock-button',
60
+ title: '切换到下一个差异文件',
61
+ 'aria-label': diffDockText(fileTotal),
62
+ onClick: onOpenNextFile,
63
+ }, diffDockText(fileTotal))))
64
+ }
65
+
27
66
  let bodyEl = null
28
67
  if (expanded) {
29
68
  const rows = pendingRegions.map((r) => {
@@ -49,32 +88,42 @@ export function DiffBox(props) {
49
88
  bodyEl = React.createElement('div', { className: 'edrv-diffbar-body' }, ...rows, ...stale, othersEl)
50
89
  }
51
90
 
52
- const menu = menuOpen
53
- ? React.createElement(React.Fragment, null,
54
- React.createElement('div', { style: { position: 'fixed', inset: 0, zIndex: 44 }, onClick: () => setMenuOpen(false) }),
55
- React.createElement('div', { className: 'edrv-diffbar-menu' },
56
- React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !allPendingCount, onClick: () => { onAcceptAllFiles(); setMenuOpen(false) } }, '✓ Keep All(全部文件采纳)'),
57
- React.createElement('button', { className: 'edrv-diffmenu-item danger', disabled: !allPendingCount, onClick: () => { onUndoAllFiles(); setMenuOpen(false) } }, '↩ Undo All(全部文件不采纳)'),
58
- React.createElement('div', { className: 'edrv-diffmenu-sep' }),
59
- React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onOpenLauncher('pending'); setMenuOpen(false) } }, '🗂 差异总览'),
60
- React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onOpenLauncher('archive'); setMenuOpen(false) } }, '📁 归档'),
61
- React.createElement('div', { className: 'edrv-diffmenu-sep' }),
62
- React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !canAct, onClick: () => { onRollback(); setMenuOpen(false) } }, '⟲ 回滚文件'),
63
- React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => { onRefresh(); setMenuOpen(false) } }, '⟳ 刷新')))
91
+ const details = detailsOpen
92
+ ? React.createElement('div', { className: 'edrv-diff-details', id: detailsId },
93
+ React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !allPendingCount, onClick: onAcceptAllFiles }, '✓ Keep All(全部文件采纳)'),
94
+ React.createElement('button', { className: 'edrv-diffmenu-item danger', disabled: !allPendingCount, onClick: onUndoAllFiles }, '↩ Undo All(全部文件不采纳)'),
95
+ React.createElement('div', { className: 'edrv-diffmenu-sep' }),
96
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => onOpenLauncher('pending') }, '🗂 差异总览'),
97
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: () => onOpenLauncher('archive') }, '📁 归档'),
98
+ React.createElement('div', { className: 'edrv-diffmenu-sep' }),
99
+ React.createElement('button', { className: 'edrv-diffmenu-item', disabled: !canAct, onClick: onRollback }, '⟲ 回滚文件'),
100
+ React.createElement('button', { className: 'edrv-diffmenu-item', onClick: onRefresh }, '⟳ 刷新'))
64
101
  : null
65
102
 
66
- return React.createElement('div', { className: 'edrv-diffbar', 'data-edrv-diffbox': '1' },
67
- React.createElement('div', { className: 'edrv-diffbar-row' },
68
- React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异', disabled: !canAct, onClick: onPrevDiff }, '↑'),
69
- React.createElement('span', { className: 'edrv-diffbar-count', title: '当前文件内差异位置(点击展开/收起差异列表)', onClick: () => setExpanded((v) => !v) }, diffTotal ? (diffIdx + 1) + '/' + diffTotal : '0/0'),
70
- React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异', disabled: !canAct, onClick: onNextDiff }, '↓'),
71
- React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异文件', disabled: !fileTotal, onClick: onPrevFile }, '←'),
72
- React.createElement('span', { className: 'edrv-diffbar-count edrv-count-file', title: '差异文件位置' }, fileTotal ? (fileIdx + 1) + '/' + fileTotal + ' 文件' : '0/0 文件'),
73
- React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异文件', disabled: !fileTotal, onClick: onNextFile }, '→'),
74
- React.createElement('span', { className: 'edrv-diffbar-file', title: activePath || '' }, base),
75
- React.createElement('button', { className: 'edrv-pill edrv-pill-keep', title: '采纳当前文件的全部差异', disabled: !canAct, onClick: onAcceptFile }, '✓ Keep'),
76
- React.createElement('button', { className: 'edrv-pill edrv-pill-undo', title: '不采纳当前文件的全部差异(回滚)', disabled: !canAct, onClick: onUndoFile }, '↩ Undo'),
77
- React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '更多操作', onClick: () => setMenuOpen((v) => !v) }, '⋮')),
103
+ return React.createElement('div', { className: 'edrv-diffbar edrv-diffbar-dock', 'data-edrv-diffbox': '1', 'data-edrv-diff-mode': 'editor' },
104
+ React.createElement('div', { className: 'edrv-diffbar-row edrv-diffbar-editor-row' },
105
+ React.createElement('div', { className: 'edrv-diffbar-editor-content' },
106
+ React.createElement('div', { className: 'edrv-diffbar-nav' },
107
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异', disabled: !canAct, onClick: onPrevDiff }, '↑'),
108
+ React.createElement('span', { className: 'edrv-diffbar-count edrv-diffbar-progress', title: '当前文件内差异位置(点击展开/收起差异列表)', onClick: () => setExpanded((v) => !v) }, diffTotal ? (diffIdx + 1) + '/' + diffTotal : '0/0'),
109
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异', disabled: !canAct, onClick: onNextDiff }, '↓')),
110
+ React.createElement('div', { className: 'edrv-diffbar-files' },
111
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '上一个差异文件', disabled: !fileTotal, onClick: onPrevFile }, '←'),
112
+ React.createElement('span', { className: 'edrv-diffbar-count edrv-count-file edrv-diffbar-progress', title: '差异文件位置' }, fileTotal ? (fileIdx + 1) + '/' + fileTotal + ' 文件' : '0/0 文件'),
113
+ React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', title: '下一个差异文件', disabled: !fileTotal, onClick: onNextFile }, '→'),
114
+ React.createElement('span', { className: 'edrv-diffbar-file edrv-diffbar-summary', title: activePath || '' }, base)),
115
+
116
+ React.createElement('div', { className: 'edrv-diffbar-actions' },
117
+ React.createElement('button', { className: 'edrv-pill edrv-pill-keep', title: '采纳当前文件的全部差异', disabled: !canAct, onClick: onAcceptFile }, '✓ Keep'),
118
+ React.createElement('button', { className: 'edrv-pill edrv-pill-undo', title: '不采纳当前文件的全部差异(回滚)', disabled: !canAct, onClick: onUndoFile }, '↩ Undo'),
119
+ React.createElement('button', {
120
+ className: 'edrv-pill edrv-pill-ghost edrv-diff-details-toggle',
121
+ title: detailsOpen ? '收起差异操作' : '展开差异操作',
122
+ 'aria-label': detailsOpen ? '收起差异操作' : '展开差异操作',
123
+ 'aria-expanded': detailsOpen,
124
+ 'aria-controls': detailsId,
125
+ onClick: () => setDetailsOpen((value) => !value),
126
+ }, chevronOf(detailsOpen))))),
78
127
  bodyEl,
79
- menu)
128
+ details)
80
129
  }