dsh-better-sidebar 0.10.0

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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/README_EN.md +139 -0
  4. package/lib/client-docx.js +7649 -0
  5. package/lib/client-editor.js +32677 -0
  6. package/lib/client-pptx.js +63438 -0
  7. package/lib/client-registry.js +8021 -0
  8. package/lib/client-terminal.js +8060 -0
  9. package/lib/client-xlsx.js +359755 -0
  10. package/lib/client.js +8021 -0
  11. package/lib/index.js +2604 -0
  12. package/lib/invariant.js +22 -0
  13. package/lib/types/agent-pty.d.ts +220 -0
  14. package/lib/types/browser-probe.d.ts +15 -0
  15. package/lib/types/bundle-route.d.ts +13 -0
  16. package/lib/types/client/BrowserView.d.ts +22 -0
  17. package/lib/types/client/DiffTab.d.ts +6 -0
  18. package/lib/types/client/DiffView.d.ts +51 -0
  19. package/lib/types/client/EditorHost.d.ts +10 -0
  20. package/lib/types/client/ExplorerView.d.ts +9 -0
  21. package/lib/types/client/GitView.d.ts +8 -0
  22. package/lib/types/client/OrphanedTab.d.ts +11 -0
  23. package/lib/types/client/PdfView.d.ts +6 -0
  24. package/lib/types/client/PptxView.d.ts +6 -0
  25. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  26. package/lib/types/client/SideCardSection.d.ts +27 -0
  27. package/lib/types/client/Sidebar.d.ts +6 -0
  28. package/lib/types/client/SubagentView.d.ts +16 -0
  29. package/lib/types/client/TabBar.d.ts +38 -0
  30. package/lib/types/client/TerminalView.d.ts +8 -0
  31. package/lib/types/client/TextEditor.d.ts +10 -0
  32. package/lib/types/client/api.d.ts +170 -0
  33. package/lib/types/client/binary-download.d.ts +12 -0
  34. package/lib/types/client/breakpoints.d.ts +12 -0
  35. package/lib/types/client/browser.d.ts +50 -0
  36. package/lib/types/client/builtins/index.d.ts +17 -0
  37. package/lib/types/client/builtins/tabs.d.ts +6 -0
  38. package/lib/types/client/builtins/viewers.d.ts +3 -0
  39. package/lib/types/client/chunk-loader.d.ts +74 -0
  40. package/lib/types/client/chunks/docx.d.ts +8 -0
  41. package/lib/types/client/chunks/editor.d.ts +9 -0
  42. package/lib/types/client/chunks/pptx.d.ts +8 -0
  43. package/lib/types/client/chunks/terminal.d.ts +9 -0
  44. package/lib/types/client/chunks/xlsx.d.ts +9 -0
  45. package/lib/types/client/cm-themes.d.ts +24 -0
  46. package/lib/types/client/conversation-draft.d.ts +14 -0
  47. package/lib/types/client/docx-view.d.ts +8 -0
  48. package/lib/types/client/editor-load.d.ts +66 -0
  49. package/lib/types/client/icons.d.ts +47 -0
  50. package/lib/types/client/image-types.d.ts +3 -0
  51. package/lib/types/client/ime-guard.d.ts +36 -0
  52. package/lib/types/client/index.d.ts +10 -0
  53. package/lib/types/client/intercept.d.ts +20 -0
  54. package/lib/types/client/lang.d.ts +12 -0
  55. package/lib/types/client/lazy-chunk.d.ts +25 -0
  56. package/lib/types/client/link-intercept.d.ts +35 -0
  57. package/lib/types/client/locales.d.ts +237 -0
  58. package/lib/types/client/office-shared.d.ts +33 -0
  59. package/lib/types/client/office-types.d.ts +36 -0
  60. package/lib/types/client/office-view.d.ts +12 -0
  61. package/lib/types/client/openpath-intercept.d.ts +41 -0
  62. package/lib/types/client/paths.d.ts +19 -0
  63. package/lib/types/client/pdf-types.d.ts +2 -0
  64. package/lib/types/client/prefs.d.ts +30 -0
  65. package/lib/types/client/produced-files.d.ts +27 -0
  66. package/lib/types/client/selection-payload.d.ts +27 -0
  67. package/lib/types/client/service.d.ts +214 -0
  68. package/lib/types/client/split-pane.d.ts +29 -0
  69. package/lib/types/client/state.d.ts +322 -0
  70. package/lib/types/client/subagent-activity.d.ts +35 -0
  71. package/lib/types/client/subagent-detect.d.ts +47 -0
  72. package/lib/types/client/subagent-jobs.d.ts +63 -0
  73. package/lib/types/client/theme.d.ts +28 -0
  74. package/lib/types/client/xlsx-to-univer.d.ts +33 -0
  75. package/lib/types/client/xlsx-view.d.ts +10 -0
  76. package/lib/types/config.d.ts +41 -0
  77. package/lib/types/context-types.d.ts +431 -0
  78. package/lib/types/fs-tree.d.ts +41 -0
  79. package/lib/types/git.d.ts +75 -0
  80. package/lib/types/html-route.d.ts +48 -0
  81. package/lib/types/index.d.ts +39 -0
  82. package/lib/types/invariant.d.ts +15 -0
  83. package/lib/types/jobs-routes.d.ts +45 -0
  84. package/lib/types/prefs-shared.d.ts +106 -0
  85. package/lib/types/pty-manager.d.ts +76 -0
  86. package/lib/types/tools.d.ts +28 -0
  87. package/lib/types/trust-fence.d.ts +24 -0
  88. package/lib/types/wire.d.ts +38 -0
  89. package/package.json +168 -0
  90. package/src/agent-pty.ts +519 -0
  91. package/src/browser-probe.ts +26 -0
  92. package/src/bundle-route.ts +130 -0
  93. package/src/client/BrowserView.tsx +253 -0
  94. package/src/client/DiffTab.tsx +110 -0
  95. package/src/client/DiffView.tsx +266 -0
  96. package/src/client/EditorHost.tsx +101 -0
  97. package/src/client/ExplorerView.tsx +283 -0
  98. package/src/client/GitView.tsx +540 -0
  99. package/src/client/OrphanedTab.tsx +27 -0
  100. package/src/client/PdfView.tsx +110 -0
  101. package/src/client/PptxView.tsx +131 -0
  102. package/src/client/SandboxStatusBar.tsx +60 -0
  103. package/src/client/SideCardSection.module.css +271 -0
  104. package/src/client/SideCardSection.tsx +426 -0
  105. package/src/client/Sidebar.tsx +861 -0
  106. package/src/client/SubagentView.module.css +547 -0
  107. package/src/client/SubagentView.tsx +866 -0
  108. package/src/client/TabBar.tsx +188 -0
  109. package/src/client/TerminalView.tsx +233 -0
  110. package/src/client/TextEditor.tsx +382 -0
  111. package/src/client/api.ts +215 -0
  112. package/src/client/binary-download.tsx +23 -0
  113. package/src/client/breakpoints.ts +51 -0
  114. package/src/client/browser.ts +119 -0
  115. package/src/client/builtins/index.ts +33 -0
  116. package/src/client/builtins/tabs.tsx +211 -0
  117. package/src/client/builtins/viewers.tsx +151 -0
  118. package/src/client/chunk-loader.ts +196 -0
  119. package/src/client/chunks/docx.tsx +8 -0
  120. package/src/client/chunks/editor.tsx +9 -0
  121. package/src/client/chunks/pptx.tsx +8 -0
  122. package/src/client/chunks/terminal.tsx +9 -0
  123. package/src/client/chunks/xlsx.tsx +9 -0
  124. package/src/client/cm-themes.ts +129 -0
  125. package/src/client/conversation-draft.ts +29 -0
  126. package/src/client/css-modules.d.ts +5 -0
  127. package/src/client/docx-view.tsx +113 -0
  128. package/src/client/editor-load.ts +92 -0
  129. package/src/client/icons.tsx +137 -0
  130. package/src/client/image-types.ts +8 -0
  131. package/src/client/ime-guard.ts +47 -0
  132. package/src/client/index.tsx +238 -0
  133. package/src/client/intercept.tsx +94 -0
  134. package/src/client/lang.ts +95 -0
  135. package/src/client/layout.css +54 -0
  136. package/src/client/lazy-chunk.tsx +89 -0
  137. package/src/client/link-intercept.ts +66 -0
  138. package/src/client/locales.ts +476 -0
  139. package/src/client/office-shared.tsx +40 -0
  140. package/src/client/office-types.ts +48 -0
  141. package/src/client/office-view.tsx +12 -0
  142. package/src/client/openpath-intercept.ts +62 -0
  143. package/src/client/paths.ts +28 -0
  144. package/src/client/pdf-types.ts +4 -0
  145. package/src/client/prefs.ts +100 -0
  146. package/src/client/produced-files.ts +87 -0
  147. package/src/client/selection-payload.ts +86 -0
  148. package/src/client/service.ts +434 -0
  149. package/src/client/sidebar.module.css +1917 -0
  150. package/src/client/split-pane.tsx +292 -0
  151. package/src/client/state.ts +1048 -0
  152. package/src/client/subagent-activity.ts +67 -0
  153. package/src/client/subagent-detect.ts +125 -0
  154. package/src/client/subagent-jobs.ts +161 -0
  155. package/src/client/theme.ts +44 -0
  156. package/src/client/xlsx-to-univer.ts +260 -0
  157. package/src/client/xlsx-view.tsx +116 -0
  158. package/src/config.ts +95 -0
  159. package/src/context-types.ts +445 -0
  160. package/src/fs-tree.ts +115 -0
  161. package/src/git.ts +236 -0
  162. package/src/html-route.ts +70 -0
  163. package/src/index.ts +897 -0
  164. package/src/invariant.ts +32 -0
  165. package/src/jobs-routes.ts +257 -0
  166. package/src/prefs-shared.ts +127 -0
  167. package/src/pty-manager.ts +199 -0
  168. package/src/tools.ts +477 -0
  169. package/src/trust-fence.ts +77 -0
  170. package/src/wire.ts +94 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * The split-pane workbench: renders the recursive split tree. A split lays
3
+ * children out row- or column-wise with draggable dividers (fractional
4
+ * sizes); a leaf renders its tab strip plus the active tab's content.
5
+ *
6
+ * Splitting is VSCode-style DRAG-TO-EDGE, not buttons: while dragging a tab
7
+ * over a pane, a drop overlay shows five zones — four edges (left/right/up/
8
+ * down) that split the pane with the tab in a fresh leaf, and the center
9
+ * that merges the tab into the pane. The tree and all operations live in
10
+ * state.ts; this file is pure presentation over them.
11
+ */
12
+ import { Fragment, useEffect, useRef, useState } from 'react'
13
+ import type { ReactNode } from 'react'
14
+ import clsx from 'clsx'
15
+ import type { SidebarState, SidebarTab, SplitNode } from './state.ts'
16
+ import type { DropZone } from './state.ts'
17
+ import { TabBar, type NewTabOption, parseDrag, type TabDragPayload } from './TabBar.tsx'
18
+ import css from './sidebar.module.css'
19
+
20
+ /** Actions the workbench needs (bound to the store by the sidebar shell). */
21
+ export interface WorkbenchActions {
22
+ closeTab: (paneId: string, tabId: string) => void
23
+ activateTab: (paneId: string, tabId: string) => void
24
+ /** Make a pane the target of newly opened tabs (click focus). */
25
+ focusPane: (paneId: string) => void
26
+ /** VSCode drag gesture: edge → split the target pane, center → merge. */
27
+ moveTabToEdge: (payload: TabDragPayload, toPane: string, zone: DropZone) => void
28
+ /** Reorder within a pane (drop onto another tab inserts before it). */
29
+ moveTabBefore: (payload: TabDragPayload, toPane: string, beforeTabId: string) => void
30
+ resizeSplit: (splitId: string, index: number, deltaFrac: number) => void
31
+ }
32
+
33
+ /** One divider: pointer-capture drag translating px deltas into fractions.
34
+ * Deltas are incremental — each move reports the displacement since the
35
+ * previous move — because the store adds every reported delta to the pane
36
+ * sizes; a cumulative (since-pointer-down) delta would be re-added on each
37
+ * move and the divider would run away from the cursor. */
38
+ function Divider(props: { dir: 'row' | 'col'; onResize: (deltaFrac: number) => void }) {
39
+ const { dir, onResize } = props
40
+ // `last` is the previous pointer position while dragging; `size` is the
41
+ // container size at pointer-down (constant during the drag), used to
42
+ // normalize the px delta into a fraction.
43
+ const last = useRef({ x: 0, y: 0, size: 0 })
44
+ const [dragging, setDragging] = useState(false)
45
+
46
+ return (
47
+ <div
48
+ className={clsx(css.divider, dir === 'row' ? css.dividerRow : css.dividerCol, dragging && css.dividerActive)}
49
+ onPointerDown={(event) => {
50
+ event.preventDefault()
51
+ event.currentTarget.setPointerCapture(event.pointerId)
52
+ const box = event.currentTarget.parentElement?.getBoundingClientRect()
53
+ last.current = {
54
+ x: event.clientX,
55
+ y: event.clientY,
56
+ size: box === undefined ? 1 : (dir === 'row' ? box.width : box.height),
57
+ }
58
+ setDragging(true)
59
+ }}
60
+ onPointerMove={(event) => {
61
+ if (!event.currentTarget.hasPointerCapture(event.pointerId)) return
62
+ const delta = dir === 'row' ? event.clientX - last.current.x : event.clientY - last.current.y
63
+ onResize(delta / Math.max(1, last.current.size))
64
+ last.current.x = event.clientX
65
+ last.current.y = event.clientY
66
+ }}
67
+ onPointerUp={(event) => {
68
+ if (!event.currentTarget.hasPointerCapture(event.pointerId)) return
69
+ event.currentTarget.releasePointerCapture(event.pointerId)
70
+ setDragging(false)
71
+ }}
72
+ />
73
+ )
74
+ }
75
+
76
+ /** Map a pointer position inside a pane to the VSCode drop zone (25% edges). */
77
+ function zoneAt(event: React.DragEvent, pane: HTMLElement): DropZone {
78
+ const rect = pane.getBoundingClientRect()
79
+ if (rect.width === 0 || rect.height === 0) return 'center'
80
+ const x = (event.clientX - rect.left) / rect.width
81
+ const y = (event.clientY - rect.top) / rect.height
82
+ if (x < 0.25) return 'left'
83
+ if (x > 0.75) return 'right'
84
+ if (y < 0.25) return 'up'
85
+ if (y > 0.75) return 'down'
86
+ return 'center'
87
+ }
88
+
89
+ /** The icon of one openable type card (mirror of the + menu options). */
90
+ /**
91
+ * An empty pane's welcome cards: the openable types as cards, clicked to
92
+ * open (instead of a bare "this pane is empty" message).
93
+ */
94
+ function PaneEmptyCards(props: {
95
+ newTabOptions: NewTabOption[]
96
+ onNewTab: (optionId: string) => void
97
+ }) {
98
+ const { newTabOptions, onNewTab } = props
99
+ return (
100
+ <div className={css.paneEmptyCards}>
101
+ {newTabOptions.map(option => (
102
+ <button
103
+ key={option.id}
104
+ type="button"
105
+ className={css.paneCard}
106
+ disabled={option.disabled === true}
107
+ title={option.label}
108
+ onClick={() => { onNewTab(option.id) }}
109
+ >
110
+ {option.icon ?? null}
111
+ <span>{option.label}</span>
112
+ </button>
113
+ ))}
114
+ </div>
115
+ )
116
+ }
117
+
118
+ /** A leaf: tab strip + active content + VSCode-style drop target for tabs. */
119
+ function LeafView(props: {
120
+ leaf: { id: string; tabs: SidebarTab[]; active: string | null }
121
+ newTabOptions: NewTabOption[]
122
+ actions: WorkbenchActions
123
+ onNewTab: (optionId: string) => void
124
+ renderTab: (tab: SidebarTab, active: boolean, paneId: string) => ReactNode
125
+ getTabIcon?: (tab: SidebarTab) => ReactNode
126
+ }) {
127
+ const { leaf, newTabOptions, actions, onNewTab, renderTab, getTabIcon } = props
128
+ const [dropZone, setDropZone] = useState<DropZone | null>(null)
129
+ const activeTab = leaf.tabs.find(tab => tab.id === leaf.active) ?? leaf.tabs[leaf.tabs.length - 1]
130
+
131
+ useEffect(() => {
132
+ const clear = (): void => { setDropZone(null) }
133
+ window.addEventListener('dragend', clear, true)
134
+ window.addEventListener('drop', clear, true)
135
+ window.addEventListener('blur', clear)
136
+ return () => {
137
+ window.removeEventListener('dragend', clear, true)
138
+ window.removeEventListener('drop', clear, true)
139
+ window.removeEventListener('blur', clear)
140
+ }
141
+ }, [])
142
+
143
+ return (
144
+ <div
145
+ className={clsx(css.pane, dropZone !== null && css.paneDrop)}
146
+ onPointerDown={() => { actions.focusPane(leaf.id) }}
147
+ onDragOver={(event) => {
148
+ event.preventDefault()
149
+ const zone = zoneAt(event, event.currentTarget)
150
+ setDropZone(zone)
151
+ }}
152
+ onDragLeave={(event) => {
153
+ // Only clear when the pointer left the pane entirely (not onto a child).
154
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
155
+ setDropZone(null)
156
+ }
157
+ }}
158
+ onDrop={(event) => {
159
+ event.preventDefault()
160
+ const zone = dropZone ?? zoneAt(event, event.currentTarget)
161
+ setDropZone(null)
162
+ const payload = parseDrag(event.dataTransfer.getData('application/x-dsh-tab'))
163
+ if (payload !== null) actions.moveTabToEdge(payload, leaf.id, zone)
164
+ }}
165
+ >
166
+ {dropZone !== null && <div className={clsx(css.dropOverlay, css[`drop${dropZone[0]!.toUpperCase()}${dropZone.slice(1)}`])} />}
167
+ {/*
168
+ The tab strip renders even for an empty pane: the + menu must stay
169
+ reachable when the pane has no tabs (fresh split, or the last tab was
170
+ dragged out), so a new tab can always be created or dragged in.
171
+ */}
172
+ <TabBar
173
+ paneId={leaf.id}
174
+ tabs={leaf.tabs}
175
+ active={leaf.active}
176
+ onActivate={(tabId) => { actions.activateTab(leaf.id, tabId) }}
177
+ onClose={(tabId) => { actions.closeTab(leaf.id, tabId) }}
178
+ onNewTab={onNewTab}
179
+ newTabOptions={newTabOptions}
180
+ getTabIcon={getTabIcon}
181
+ onDropTab={(payload, before) => {
182
+ if (before === null) actions.moveTabToEdge(payload, leaf.id, 'center')
183
+ else actions.moveTabBefore(payload, leaf.id, before)
184
+ }}
185
+ />
186
+ {leaf.tabs.length > 0 ? (
187
+ /*
188
+ Every tab stays MOUNTED (inactive ones hidden), so switching tabs
189
+ never tears down the content: a terminal keeps its pty connection
190
+ and scrollback, an editor keeps its CodeMirror view and unsaved
191
+ draft, explorer/git keep their loaded data. The unmount (and the
192
+ terminal's close frame) happens only when a tab is truly closed.
193
+ */
194
+ <div className={css.paneContent}>
195
+ {leaf.tabs.map(tab => (
196
+ <div
197
+ key={tab.id}
198
+ className={clsx(css.paneTab, tab.id !== activeTab?.id && css.paneTabHidden)}
199
+ >
200
+ {renderTab(tab, tab.id === activeTab?.id, leaf.id)}
201
+ </div>
202
+ ))}
203
+ </div>
204
+ ) : (
205
+ <PaneEmptyCards newTabOptions={newTabOptions} onNewTab={onNewTab} />
206
+ )}
207
+ </div>
208
+ )
209
+ }
210
+
211
+ /** Recursive node renderer. */
212
+ function NodeView(props: {
213
+ node: SplitNode
214
+ state: SidebarState
215
+ newTabOptions: NewTabOption[]
216
+ actions: WorkbenchActions
217
+ onNewTab: (optionId: string) => void
218
+ renderTab: (tab: SidebarTab, active: boolean, paneId: string) => ReactNode
219
+ getTabIcon?: (tab: SidebarTab) => ReactNode
220
+ }) {
221
+ const { node, state, newTabOptions, actions, onNewTab, renderTab, getTabIcon } = props
222
+ if (node.kind === 'leaf') {
223
+ return (
224
+ <LeafView
225
+ leaf={node}
226
+ newTabOptions={newTabOptions}
227
+ actions={actions}
228
+ onNewTab={onNewTab}
229
+ renderTab={renderTab}
230
+ getTabIcon={getTabIcon}
231
+ />
232
+ )
233
+ }
234
+ const isRow = node.dir === 'row'
235
+ return (
236
+ <div className={clsx(css.split, isRow ? css.splitRow : css.splitCol)}>
237
+ {node.children.map((child, index) => (
238
+ <Fragment key={child.id}>
239
+ {index > 0 && (
240
+ <Divider
241
+ dir={node.dir}
242
+ onResize={(deltaFrac) => { actions.resizeSplit(node.id, index - 1, deltaFrac) }}
243
+ />
244
+ )}
245
+ <div
246
+ className={css.splitChild}
247
+ style={{ flexGrow: node.sizes[index], flexBasis: 0, minWidth: 0, minHeight: 0 }}
248
+ >
249
+ <NodeView
250
+ node={child}
251
+ state={state}
252
+ newTabOptions={newTabOptions}
253
+ actions={actions}
254
+ onNewTab={onNewTab}
255
+ renderTab={renderTab}
256
+ getTabIcon={getTabIcon}
257
+ />
258
+ </div>
259
+ </Fragment>
260
+ ))}
261
+ </div>
262
+ )
263
+ }
264
+
265
+ /** The workbench: the split tree filling the sidebar body. `tree` selects
266
+ * which tree renders (the right panel's by default; the bottom panel passes
267
+ * `state.bottomSplits` — the actions route by pane id, so one action set
268
+ * serves both). */
269
+ export function Workbench(props: {
270
+ state: SidebarState
271
+ tree?: SplitNode
272
+ newTabOptions: NewTabOption[]
273
+ actions: WorkbenchActions
274
+ onNewTab: (optionId: string) => void
275
+ renderTab: (tab: SidebarTab, active: boolean, paneId: string) => ReactNode
276
+ getTabIcon?: (tab: SidebarTab) => ReactNode
277
+ }) {
278
+ const { state, tree, newTabOptions, actions, onNewTab, renderTab, getTabIcon } = props
279
+ return (
280
+ <div className={css.workbench}>
281
+ <NodeView
282
+ node={tree ?? state.splits}
283
+ state={state}
284
+ newTabOptions={newTabOptions}
285
+ actions={actions}
286
+ onNewTab={onNewTab}
287
+ renderTab={renderTab}
288
+ getTabIcon={getTabIcon}
289
+ />
290
+ </div>
291
+ )
292
+ }