@cerbur/clutch-dsh-worktree 0.1.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 (114) hide show
  1. package/README.md +136 -0
  2. package/cordis.patch.yml +5 -0
  3. package/lib/client/WorktreeModeAction.d.ts +11 -0
  4. package/lib/client/WorktreeModeAction.d.ts.map +1 -0
  5. package/lib/client/WorktreeModeAction.js +14 -0
  6. package/lib/client/WorktreeModeAction.js.map +1 -0
  7. package/lib/client/WorktreeSurface.d.ts +33 -0
  8. package/lib/client/WorktreeSurface.d.ts.map +1 -0
  9. package/lib/client/WorktreeSurface.js +834 -0
  10. package/lib/client/WorktreeSurface.js.map +1 -0
  11. package/lib/client/entry.d.ts +18 -0
  12. package/lib/client/entry.d.ts.map +1 -0
  13. package/lib/client/entry.js +100 -0
  14. package/lib/client/entry.js.map +1 -0
  15. package/lib/client/index.d.ts +7 -0
  16. package/lib/client/index.d.ts.map +1 -0
  17. package/lib/client/index.js +6 -0
  18. package/lib/client/index.js.map +1 -0
  19. package/lib/client/navigation.d.ts +7 -0
  20. package/lib/client/navigation.d.ts.map +1 -0
  21. package/lib/client/navigation.js +5 -0
  22. package/lib/client/navigation.js.map +1 -0
  23. package/lib/client/overlay-bounds.d.ts +11 -0
  24. package/lib/client/overlay-bounds.d.ts.map +1 -0
  25. package/lib/client/overlay-bounds.js +9 -0
  26. package/lib/client/overlay-bounds.js.map +1 -0
  27. package/lib/client/sidebar-overlay-geometry.d.ts +10 -0
  28. package/lib/client/sidebar-overlay-geometry.d.ts.map +1 -0
  29. package/lib/client/sidebar-overlay-geometry.js +96 -0
  30. package/lib/client/sidebar-overlay-geometry.js.map +1 -0
  31. package/lib/client/view-mode-store.d.ts +5 -0
  32. package/lib/client/view-mode-store.d.ts.map +1 -0
  33. package/lib/client/view-mode-store.js +15 -0
  34. package/lib/client/view-mode-store.js.map +1 -0
  35. package/lib/client/view-mode.d.ts +52 -0
  36. package/lib/client/view-mode.d.ts.map +1 -0
  37. package/lib/client/view-mode.js +76 -0
  38. package/lib/client/view-mode.js.map +1 -0
  39. package/lib/client/virtual-workspace-membership.d.ts +27 -0
  40. package/lib/client/virtual-workspace-membership.d.ts.map +1 -0
  41. package/lib/client/virtual-workspace-membership.js +95 -0
  42. package/lib/client/virtual-workspace-membership.js.map +1 -0
  43. package/lib/client/worktree-connection.d.ts +42 -0
  44. package/lib/client/worktree-connection.d.ts.map +1 -0
  45. package/lib/client/worktree-connection.js +139 -0
  46. package/lib/client/worktree-connection.js.map +1 -0
  47. package/lib/client/worktree-view.d.ts +63 -0
  48. package/lib/client/worktree-view.d.ts.map +1 -0
  49. package/lib/client/worktree-view.js +116 -0
  50. package/lib/client/worktree-view.js.map +1 -0
  51. package/lib/client.js +1952 -0
  52. package/lib/client.js.map +1 -0
  53. package/lib/contract/index.contract.d.ts +2 -0
  54. package/lib/contract/index.contract.d.ts.map +1 -0
  55. package/lib/contract/index.contract.js +51 -0
  56. package/lib/contract/index.contract.js.map +1 -0
  57. package/lib/contract/index.d.ts +181 -0
  58. package/lib/contract/index.d.ts.map +1 -0
  59. package/lib/contract/index.js +45 -0
  60. package/lib/contract/index.js.map +1 -0
  61. package/lib/host/dsh-read-adapter.d.ts +58 -0
  62. package/lib/host/dsh-read-adapter.d.ts.map +1 -0
  63. package/lib/host/dsh-read-adapter.js +59 -0
  64. package/lib/host/dsh-read-adapter.js.map +1 -0
  65. package/lib/host/index.d.ts +16 -0
  66. package/lib/host/index.d.ts.map +1 -0
  67. package/lib/host/index.js +14 -0
  68. package/lib/host/index.js.map +1 -0
  69. package/lib/host/remote.d.ts +16 -0
  70. package/lib/host/remote.d.ts.map +1 -0
  71. package/lib/host/remote.js +61 -0
  72. package/lib/host/remote.js.map +1 -0
  73. package/lib/host/service.d.ts +58 -0
  74. package/lib/host/service.d.ts.map +1 -0
  75. package/lib/host/service.js +121 -0
  76. package/lib/host/service.js.map +1 -0
  77. package/lib/index.d.ts +23 -0
  78. package/lib/index.d.ts.map +1 -0
  79. package/lib/index.js +22 -0
  80. package/lib/index.js.map +1 -0
  81. package/lib/manage/index.d.ts +9 -0
  82. package/lib/manage/index.d.ts.map +1 -0
  83. package/lib/manage/index.js +8 -0
  84. package/lib/manage/index.js.map +1 -0
  85. package/lib/manage/manager.d.ts +86 -0
  86. package/lib/manage/manager.d.ts.map +1 -0
  87. package/lib/manage/manager.js +584 -0
  88. package/lib/manage/manager.js.map +1 -0
  89. package/lib/manage/types.d.ts +41 -0
  90. package/lib/manage/types.d.ts.map +1 -0
  91. package/lib/manage/types.js +2 -0
  92. package/lib/manage/types.js.map +1 -0
  93. package/lib/provider/git.d.ts +42 -0
  94. package/lib/provider/git.d.ts.map +1 -0
  95. package/lib/provider/git.js +222 -0
  96. package/lib/provider/git.js.map +1 -0
  97. package/lib/provider/index.d.ts +13 -0
  98. package/lib/provider/index.d.ts.map +1 -0
  99. package/lib/provider/index.js +12 -0
  100. package/lib/provider/index.js.map +1 -0
  101. package/lib/provider/sidecar.d.ts +85 -0
  102. package/lib/provider/sidecar.d.ts.map +1 -0
  103. package/lib/provider/sidecar.js +410 -0
  104. package/lib/provider/sidecar.js.map +1 -0
  105. package/lib/provider/types.d.ts +126 -0
  106. package/lib/provider/types.d.ts.map +1 -0
  107. package/lib/provider/types.js +46 -0
  108. package/lib/provider/types.js.map +1 -0
  109. package/lib/typert.host.d.ts +3 -0
  110. package/lib/typert.host.js +290 -0
  111. package/lib/typert.remote-client.d.ts +32 -0
  112. package/lib/typert.remote-client.d.ts.map +1 -0
  113. package/lib/typert.remote-client.js +284 -0
  114. package/package.json +118 -0
@@ -0,0 +1,834 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Button, IconArchiveOutline20, IconBranchOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFolderClose16, IconFolderOpen16, IconPlusOutline16, IconSearchOutline16, IconTrashOutline16, Input, Menu, Modal, StateDot, } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { openWorktreeSession } from './navigation.js';
5
+ import { useSidebarOverlayGeometry } from './sidebar-overlay-geometry.js';
6
+ import { effectiveViewMode, unboundSessionIds, workspaceSessionIds } from './view-mode.js';
7
+ import { executeWorktreeAction, createDefaultWorktreeName, filterArchivedSessionIds, loadWorktreeViews, selectDefaultBaseBranch, stableWorkspaceIds, toWorktreeViewError, WorktreeSessionBindingError, } from './worktree-view.js';
8
+ import styles from './worktree.css';
9
+ const EMPTY_READ_STATE = { status: 'idle', views: [] };
10
+ function useStableWorkspaceIds(workspaces) {
11
+ const next = workspaces.map((workspace) => workspace.workspaceId);
12
+ const previousRef = useRef([]);
13
+ const stable = stableWorkspaceIds(previousRef.current, next);
14
+ if (stable !== previousRef.current)
15
+ previousRef.current = stable;
16
+ return previousRef.current;
17
+ }
18
+ function sessionLabel(sessionId, sessions) {
19
+ return sessions.byId[sessionId]?.displayTitle ?? sessionId;
20
+ }
21
+ function rowHalf(event) {
22
+ const rect = event.currentTarget.getBoundingClientRect();
23
+ return event.clientY < rect.top + rect.height / 2 ? 'before' : 'after';
24
+ }
25
+ function bindingIdsFor(bindings, worktreeId) {
26
+ return bindings
27
+ .filter((binding) => binding.worktreeId === worktreeId)
28
+ .map((binding) => binding.sessionId);
29
+ }
30
+ function includesText(value, query) {
31
+ return value.toLocaleLowerCase().includes(query);
32
+ }
33
+ function workspaceMatches(workspace, view, sessions, query) {
34
+ if (query.length === 0)
35
+ return true;
36
+ if (includesText(workspace.title, query))
37
+ return true;
38
+ if (workspace.sessionIds.some((sessionId) => includesText(sessionLabel(sessionId, sessions), query))) {
39
+ return true;
40
+ }
41
+ if (view === undefined)
42
+ return false;
43
+ if (view.worktrees.some((record) => includesText(record.branch, query) || includesText(record.absolutePath, query))) {
44
+ return true;
45
+ }
46
+ return view.bindings.some((binding) => includesText(sessionLabel(binding.sessionId, sessions), query));
47
+ }
48
+ /** Workspace row using the native DSH menu, fixed action column, and drag contract. */
49
+ function WorktreeWorkspaceRow({ workspace, expanded, actionPending, menuOpen, drag, onToggle, onCreateWorktree, onRename, onDelete, onMenuOpenChange, }) {
50
+ const workspaceMenuItems = [
51
+ {
52
+ id: 'rename',
53
+ label: 'Rename',
54
+ icon: _jsx(IconEditOutline16, {}),
55
+ disabled: actionPending,
56
+ },
57
+ {
58
+ id: 'delete',
59
+ label: 'Delete',
60
+ icon: _jsx(IconTrashOutline16, {}),
61
+ danger: true,
62
+ disabled: actionPending,
63
+ },
64
+ ];
65
+ const markerClass = drag.marker === 'before'
66
+ ? styles.dropBefore
67
+ : drag.marker === 'after'
68
+ ? styles.dropAfter
69
+ : '';
70
+ return (_jsxs("div", { className: `${styles.workspaceRow} ${markerClass}`, "data-workspace-drag": drag.active ? 'active' : undefined, "data-menu-open": menuOpen || undefined, draggable: true, onClick: () => {
71
+ onToggle();
72
+ }, onDragStart: (event) => {
73
+ event.dataTransfer.effectAllowed = 'move';
74
+ event.dataTransfer.setData('text/plain', workspace.workspaceId);
75
+ drag.start();
76
+ }, onDragEnd: drag.end, onDragOver: (event) => {
77
+ if (!drag.active)
78
+ return;
79
+ event.preventDefault();
80
+ event.dataTransfer.dropEffect = 'move';
81
+ drag.hover(rowHalf(event));
82
+ }, onDrop: (event) => {
83
+ if (!drag.active)
84
+ return;
85
+ event.preventDefault();
86
+ drag.drop(rowHalf(event));
87
+ }, children: [_jsx("button", { type: "button", className: `${styles.disclosureButton} ${styles.workspaceDisclosure}`, "aria-label": `${expanded ? 'Collapse' : 'Expand'} ${workspace.title}`, "aria-expanded": expanded, onClick: (event) => {
88
+ event.stopPropagation();
89
+ onToggle();
90
+ }, children: expanded ? (_jsx(IconChevronDownOutline14, { size: 18 })) : (_jsx(IconChevronRightOutline14, { size: 18 })) }), _jsx("span", { className: styles.workspaceIcon, "aria-hidden": "true", children: expanded ? _jsx(IconFolderOpen16, {}) : _jsx(IconFolderClose16, {}) }), _jsx("span", { className: styles.workspaceTitle, children: workspace.title }), _jsxs("span", { className: `${styles.treeActionSlot} ${styles.workspaceActions}`, children: [_jsx("span", { className: styles.menuAction, children: _jsx(Menu, { open: menuOpen, onClose: () => {
91
+ onMenuOpenChange(false);
92
+ }, items: workspaceMenuItems, onSelect: (id) => {
93
+ onMenuOpenChange(false);
94
+ if (id === 'rename')
95
+ onRename();
96
+ if (id === 'delete')
97
+ onDelete();
98
+ }, portal: true, closeOnPointerLeave: true, anchor: (_jsx("button", { type: "button", className: styles.iconButton, "data-workspace-menu": true, "aria-label": `Workspace options for ${workspace.title}`, onClick: (event) => {
99
+ event.stopPropagation();
100
+ onMenuOpenChange(!menuOpen);
101
+ }, children: _jsx(IconEllipsisOutline16, {}) })) }) }), _jsx("button", { type: "button", className: styles.iconButton, "data-add-worktree": true, "aria-label": `Add Worktree to ${workspace.title}`, onClick: (event) => {
102
+ event.stopPropagation();
103
+ onCreateWorktree();
104
+ }, children: _jsx(IconPlusOutline16, {}) })] })] }));
105
+ }
106
+ /** Shared Main/Worktree group row with parameterized icon, state, and actions. */
107
+ function WorktreeGroupRow({ kind, label, expanded, icon, workspaceTitle, state, stateLabel, onToggle, onCreateSession, }) {
108
+ const main = kind === 'main';
109
+ return (_jsxs("div", { className: styles.worktreeRow, "data-main-group": main ? 'true' : undefined, "data-main-expanded": main ? String(expanded) : undefined, onClick: onToggle, children: [_jsx("button", { type: "button", className: `${styles.disclosureButton} ${styles.worktreeDisclosure}`, "aria-label": `${expanded ? 'Collapse' : 'Expand'} ${label}`, "aria-expanded": expanded, onClick: (event) => {
110
+ event.stopPropagation();
111
+ onToggle();
112
+ }, children: expanded ? (_jsx(IconChevronDownOutline14, { size: 18 })) : (_jsx(IconChevronRightOutline14, { size: 18 })) }), _jsx("span", { className: styles.worktreeIcon, "aria-hidden": "true", children: icon }), state !== undefined && stateLabel !== undefined && (_jsx("span", { className: styles.worktreeState, role: "img", "aria-label": stateLabel, title: stateLabel, children: _jsx(StateDot, { state: state }) })), _jsx("span", { className: styles.worktreeLabel, children: label }), _jsx("span", { className: styles.treeActionSlot, children: onCreateSession !== undefined && (_jsx("button", { type: "button", className: styles.iconButton, "data-add-main-session": main ? 'true' : undefined, "data-add-session": main ? undefined : 'true', "aria-label": `Add Session to ${workspaceTitle}`, onClick: (event) => {
113
+ event.stopPropagation();
114
+ onCreateSession();
115
+ }, children: _jsx(IconPlusOutline16, {}) })) })] }));
116
+ }
117
+ /** Worktree-mode Session row with the same trailing options menu as native DSH rows. */
118
+ function WorktreeSessionRow({ sessionId, label, drag, actionPending, onOpen, onRename, onFork, onArchive, }) {
119
+ const [menuOpen, setMenuOpen] = useState(false);
120
+ const markerClass = drag.marker === 'before'
121
+ ? styles.dropBefore
122
+ : drag.marker === 'after'
123
+ ? styles.dropAfter
124
+ : '';
125
+ const sessionMenuItems = [
126
+ {
127
+ id: 'rename',
128
+ label: 'Rename',
129
+ icon: _jsx(IconEditOutline16, {}),
130
+ disabled: onRename === undefined || actionPending,
131
+ },
132
+ {
133
+ id: 'fork',
134
+ label: 'Fork session',
135
+ icon: _jsx(IconBranchOutline16, {}),
136
+ disabled: onFork === undefined || actionPending,
137
+ },
138
+ {
139
+ id: 'archive',
140
+ label: 'Archive session',
141
+ icon: _jsx(IconArchiveOutline20, { size: 16 }),
142
+ disabled: onArchive === undefined || actionPending,
143
+ },
144
+ ];
145
+ return (_jsxs("div", { className: `${styles.treeSessionRow} ${markerClass}`, "data-session-id": sessionId, "data-session-drag": drag.active ? 'active' : undefined, "data-menu-open": menuOpen || undefined, role: "treeitem", draggable: true, onDragStart: (event) => {
146
+ event.dataTransfer.effectAllowed = 'move';
147
+ event.dataTransfer.setData('text/plain', sessionId);
148
+ drag.start();
149
+ }, onDragEnd: drag.end, onDragOver: (event) => {
150
+ if (!drag.active)
151
+ return;
152
+ event.preventDefault();
153
+ event.dataTransfer.dropEffect = 'move';
154
+ drag.hover(rowHalf(event));
155
+ }, onDrop: (event) => {
156
+ if (!drag.active)
157
+ return;
158
+ event.preventDefault();
159
+ drag.drop(rowHalf(event));
160
+ }, children: [_jsxs("button", { type: "button", className: styles.treeSessionContent, onClick: onOpen, children: [_jsx("span", { className: styles.treeGuide, "aria-hidden": "true", children: "\u2514" }), _jsx("span", { className: styles.sessionLabel, children: label })] }), _jsx("span", { className: styles.rowActions, children: _jsx(Menu, { open: menuOpen, onClose: () => {
161
+ setMenuOpen(false);
162
+ }, items: sessionMenuItems, onSelect: (id) => {
163
+ setMenuOpen(false);
164
+ if (id === 'rename')
165
+ onRename?.(sessionId, label);
166
+ if (id === 'fork')
167
+ onFork?.(sessionId);
168
+ if (id === 'archive')
169
+ onArchive?.(sessionId);
170
+ }, portal: true, closeOnPointerLeave: true, anchor: (_jsx("button", { type: "button", className: styles.iconButton, "data-session-menu": true, "aria-label": `Session options for ${label}`, onClick: (event) => {
171
+ event.stopPropagation();
172
+ setMenuOpen((current) => !current);
173
+ }, children: _jsx(IconEllipsisOutline16, {}) })) }) })] }));
174
+ }
175
+ function WorktreeSessionGroup({ groupKey, sessionIds, workspaceId, expanded, actionPending, sessions, dragState, onToggleExpanded, onStartDrag, onHoverDrag, onClearDrag, onFinishDrag, onCommitDrag, onOpen, onRename, onFork, onArchive, }) {
176
+ const visibleSessionIds = expanded ? sessionIds : sessionIds.slice(0, 5);
177
+ const sameGroupDrag = dragState?.groupKey === groupKey;
178
+ return (_jsxs(_Fragment, { children: [visibleSessionIds.map((sessionId) => (_jsx(WorktreeSessionRow, { sessionId: sessionId, label: sessionLabel(sessionId, sessions), drag: {
179
+ active: sameGroupDrag,
180
+ marker: sameGroupDrag && dragState.over?.sessionId === sessionId
181
+ ? dragState.over.half
182
+ : null,
183
+ start: () => {
184
+ onStartDrag(groupKey, sessionId);
185
+ },
186
+ hover: (half) => {
187
+ onHoverDrag(sessionId, half);
188
+ },
189
+ drop: (half) => {
190
+ if (dragState === undefined)
191
+ return;
192
+ onCommitDrag(dragState, { sessionId, half }, sessionIds, workspaceId);
193
+ },
194
+ end: () => {
195
+ if (dragState?.over !== null && dragState?.over !== undefined) {
196
+ onCommitDrag(dragState, dragState.over, sessionIds, workspaceId);
197
+ }
198
+ else {
199
+ onClearDrag();
200
+ }
201
+ onFinishDrag();
202
+ },
203
+ }, actionPending: actionPending, onOpen: () => {
204
+ onOpen(sessionId);
205
+ }, onRename: onRename, onFork: onFork, onArchive: onArchive }, `${groupKey}:${sessionId}`))), sessionIds.length > 5 && (_jsx("button", { type: "button", className: styles.sessionOverflowButton, "aria-expanded": expanded, onClick: onToggleExpanded, children: expanded ? 'Collapse' : `Expand ${sessionIds.length - 5} more` }))] }));
206
+ }
207
+ /**
208
+ * Peer Worktree navigation surface. It keeps the original DSH Workspace mode
209
+ * intact and renders a browser-local Workspace → Worktree → Session projection.
210
+ */
211
+ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, available, manager, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, renameSession, forkSession, archiveSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
212
+ const preferredMode = useStore((state) => state.viewMode);
213
+ const mode = effectiveViewMode(preferredMode, available && manager !== undefined);
214
+ const sessions = useSessions((state) => state);
215
+ const workspaces = useWorkspaces((state) => state);
216
+ const workspaceIds = useStableWorkspaceIds(workspaces.items);
217
+ const [readState, setReadState] = useState(EMPTY_READ_STATE);
218
+ const [searchQuery, setSearchQuery] = useState('');
219
+ const [expandedWorkspaces, setExpandedWorkspaces] = useState({});
220
+ const [expandedMains, setExpandedMains] = useState({});
221
+ const [expandedWorktrees, setExpandedWorktrees] = useState({});
222
+ const [worktreeModalWorkspaceId, setWorktreeModalWorkspaceId] = useState();
223
+ const [openWorkspaceMenuId, setOpenWorkspaceMenuId] = useState();
224
+ const [selectedBranch, setSelectedBranch] = useState('');
225
+ const [newBranch, setNewBranch] = useState('');
226
+ const [pendingSessionBinding, setPendingSessionBinding] = useState();
227
+ const [actionError, setActionError] = useState();
228
+ const [actionPending, setActionPending] = useState(false);
229
+ const [sessionRenameTarget, setSessionRenameTarget] = useState();
230
+ const [sessionRenameDraft, setSessionRenameDraft] = useState('');
231
+ const [sessionRenamePending, setSessionRenamePending] = useState(false);
232
+ const [sessionRenameError, setSessionRenameError] = useState();
233
+ const [workspaceDrag, setWorkspaceDrag] = useState();
234
+ const [workspaceRenameTarget, setWorkspaceRenameTarget] = useState();
235
+ const [workspaceRenameDraft, setWorkspaceRenameDraft] = useState('');
236
+ const [workspaceRenamePending, setWorkspaceRenamePending] = useState(false);
237
+ const [workspaceRenameError, setWorkspaceRenameError] = useState();
238
+ const [workspaceDeleteTarget, setWorkspaceDeleteTarget] = useState();
239
+ const [workspaceDeletePending, setWorkspaceDeletePending] = useState(false);
240
+ const [workspaceDeleteError, setWorkspaceDeleteError] = useState();
241
+ const workspaceDropCommitted = useRef(false);
242
+ const [sessionDrag, setSessionDrag] = useState();
243
+ const [expandedSessionGroups, setExpandedSessionGroups] = useState({});
244
+ const sessionDropCommitted = useRef(false);
245
+ const { ref, width, bounds } = useSidebarOverlayGeometry(mode === 'worktree');
246
+ const collapsed = width <= 64;
247
+ const query = searchQuery.trim().toLocaleLowerCase();
248
+ const archivedSessionIds = workspaces.archivedSessionIds ?? [];
249
+ const refresh = useCallback(async () => {
250
+ if (manager === undefined) {
251
+ setReadState(EMPTY_READ_STATE);
252
+ return;
253
+ }
254
+ setReadState({ status: 'loading', views: [] });
255
+ try {
256
+ const views = await loadWorktreeViews(manager, workspaceIds);
257
+ setReadState({ status: 'ready', views });
258
+ }
259
+ catch (error) {
260
+ setReadState({
261
+ status: 'error',
262
+ views: [],
263
+ error: toWorktreeViewError(error),
264
+ });
265
+ }
266
+ }, [manager, workspaceIds]);
267
+ useEffect(() => {
268
+ if (mode === 'worktree') {
269
+ void refresh();
270
+ }
271
+ else {
272
+ setReadState(EMPTY_READ_STATE);
273
+ }
274
+ }, [mode, refresh]);
275
+ useEffect(() => {
276
+ if (readState.status !== 'ready' || syncSessionWorkspaces === undefined)
277
+ return;
278
+ syncSessionWorkspaces(readState.views.flatMap((view) => view.bindings.map((binding) => ({
279
+ workspaceId: binding.workspaceId,
280
+ sessionId: binding.sessionId,
281
+ }))));
282
+ }, [readState.status, readState.views, syncSessionWorkspaces]);
283
+ const viewByWorkspace = useMemo(() => new Map(readState.views.map((view) => [view.workspaceId, view])), [readState.views]);
284
+ const modalWorkspace = workspaces.items.find((workspace) => workspace.workspaceId === worktreeModalWorkspaceId);
285
+ const modalView = worktreeModalWorkspaceId === undefined
286
+ ? undefined
287
+ : viewByWorkspace.get(worktreeModalWorkspaceId);
288
+ const runMutation = async (operation) => {
289
+ setActionPending(true);
290
+ setActionError(undefined);
291
+ try {
292
+ await operation();
293
+ await refresh();
294
+ }
295
+ catch (error) {
296
+ setActionError(toWorktreeViewError(error));
297
+ }
298
+ finally {
299
+ setActionPending(false);
300
+ }
301
+ };
302
+ const workspaceRenameTrimmed = workspaceRenameDraft.trim();
303
+ const workspaceRenameDuplicate = workspaceRenameTarget !== undefined &&
304
+ workspaceRenameTrimmed.length > 0 &&
305
+ workspaces.items.some((workspace) => workspace.workspaceId !== workspaceRenameTarget.workspaceId &&
306
+ workspace.title === workspaceRenameTrimmed);
307
+ const workspaceRenameBlocked = workspaceRenamePending ||
308
+ workspaceRenameTarget === undefined ||
309
+ workspaceRenameTrimmed.length === 0 ||
310
+ workspaceRenameTrimmed === workspaceRenameTarget?.currentTitle ||
311
+ workspaceRenameDuplicate;
312
+ const openWorkspaceRename = (workspace) => {
313
+ setWorkspaceRenameTarget({
314
+ workspaceId: workspace.workspaceId,
315
+ currentTitle: workspace.title,
316
+ });
317
+ setWorkspaceRenameDraft(workspace.title);
318
+ setWorkspaceRenameError(undefined);
319
+ };
320
+ const closeWorkspaceRename = () => {
321
+ if (workspaceRenamePending)
322
+ return;
323
+ setWorkspaceRenameTarget(undefined);
324
+ setWorkspaceRenameError(undefined);
325
+ };
326
+ const confirmWorkspaceRename = async () => {
327
+ const target = workspaceRenameTarget;
328
+ if (workspaceRenameBlocked || target === undefined)
329
+ return;
330
+ if (renameWorkspace === undefined) {
331
+ setWorkspaceRenameError('Workspace rename is unavailable; retry after reconnecting.');
332
+ return;
333
+ }
334
+ setWorkspaceRenamePending(true);
335
+ setWorkspaceRenameError(undefined);
336
+ try {
337
+ await renameWorkspace(target.workspaceId, workspaceRenameTrimmed);
338
+ setWorkspaceRenameTarget(undefined);
339
+ }
340
+ catch (error) {
341
+ setWorkspaceRenameError(error instanceof Error ? error.message : String(error));
342
+ }
343
+ finally {
344
+ setWorkspaceRenamePending(false);
345
+ }
346
+ };
347
+ const openWorkspaceDelete = (workspace) => {
348
+ setWorkspaceDeleteTarget({ workspaceId: workspace.workspaceId, title: workspace.title });
349
+ setWorkspaceDeleteError(undefined);
350
+ };
351
+ const closeWorkspaceDelete = () => {
352
+ if (workspaceDeletePending)
353
+ return;
354
+ setWorkspaceDeleteTarget(undefined);
355
+ setWorkspaceDeleteError(undefined);
356
+ };
357
+ const confirmWorkspaceDelete = async () => {
358
+ const target = workspaceDeleteTarget;
359
+ if (target === undefined || workspaceDeletePending)
360
+ return;
361
+ if (deleteWorkspace === undefined) {
362
+ setWorkspaceDeleteError('Workspace delete is unavailable; retry after reconnecting.');
363
+ return;
364
+ }
365
+ setWorkspaceDeletePending(true);
366
+ setWorkspaceDeleteError(undefined);
367
+ try {
368
+ await deleteWorkspace(target.workspaceId);
369
+ setWorkspaceDeleteTarget(undefined);
370
+ }
371
+ catch (error) {
372
+ setWorkspaceDeleteError(error instanceof Error ? error.message : String(error));
373
+ }
374
+ finally {
375
+ setWorkspaceDeletePending(false);
376
+ }
377
+ };
378
+ const commitWorkspaceDrag = (activeDrag, over) => {
379
+ if (workspaceDropCommitted.current)
380
+ return;
381
+ workspaceDropCommitted.current = true;
382
+ setWorkspaceDrag(undefined);
383
+ const rows = workspaces.items;
384
+ const targetIndex = rows.findIndex((workspace) => workspace.workspaceId === over.workspaceId);
385
+ const sourceIndex = rows.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
386
+ if (targetIndex === -1 || sourceIndex === -1)
387
+ return;
388
+ const beforeWorkspaceId = over.half === 'before'
389
+ ? over.workspaceId
390
+ : rows[targetIndex + 1]?.workspaceId;
391
+ const anchorIndex = beforeWorkspaceId === undefined
392
+ ? rows.length
393
+ : rows.findIndex((workspace) => workspace.workspaceId === beforeWorkspaceId);
394
+ if (beforeWorkspaceId === activeDrag.workspaceId ||
395
+ anchorIndex === sourceIndex ||
396
+ anchorIndex === sourceIndex + 1) {
397
+ return;
398
+ }
399
+ if (insertWorkspaceBefore === undefined) {
400
+ setActionError({
401
+ code: 'WORKSPACE_ORDER_UNAVAILABLE',
402
+ message: 'Workspace ordering is unavailable; retry after reconnecting.',
403
+ retryable: true,
404
+ });
405
+ return;
406
+ }
407
+ setActionError(undefined);
408
+ void insertWorkspaceBefore(activeDrag.workspaceId, beforeWorkspaceId).catch((error) => {
409
+ setActionError(toWorktreeViewError(error));
410
+ });
411
+ };
412
+ const commitSessionDrag = (activeDrag, over, sessionIds, workspaceId) => {
413
+ if (sessionDropCommitted.current)
414
+ return;
415
+ sessionDropCommitted.current = true;
416
+ setSessionDrag(undefined);
417
+ const targetIndex = sessionIds.indexOf(over.sessionId);
418
+ const sourceIndex = sessionIds.indexOf(activeDrag.sessionId);
419
+ if (targetIndex === -1 || sourceIndex === -1)
420
+ return;
421
+ const beforeSessionId = over.half === 'before'
422
+ ? over.sessionId
423
+ : sessionIds[targetIndex + 1];
424
+ const anchorIndex = beforeSessionId === undefined
425
+ ? sessionIds.length
426
+ : sessionIds.indexOf(beforeSessionId);
427
+ if (beforeSessionId === activeDrag.sessionId ||
428
+ anchorIndex === sourceIndex ||
429
+ anchorIndex === sourceIndex + 1) {
430
+ return;
431
+ }
432
+ if (insertSessionBefore === undefined) {
433
+ setActionError({
434
+ code: 'SESSION_ORDER_UNAVAILABLE',
435
+ message: 'Session ordering is unavailable; retry after reconnecting.',
436
+ retryable: true,
437
+ });
438
+ return;
439
+ }
440
+ setActionError(undefined);
441
+ void insertSessionBefore(workspaceId, activeDrag.sessionId, beforeSessionId).catch((error) => {
442
+ setActionError(toWorktreeViewError(error));
443
+ });
444
+ };
445
+ const openSessionRename = (sessionId, currentTitle) => {
446
+ setSessionRenameTarget({ sessionId, currentTitle });
447
+ setSessionRenameDraft(currentTitle);
448
+ setSessionRenameError(undefined);
449
+ };
450
+ const closeSessionRename = () => {
451
+ if (sessionRenamePending)
452
+ return;
453
+ setSessionRenameTarget(undefined);
454
+ setSessionRenameError(undefined);
455
+ };
456
+ const confirmSessionRename = async () => {
457
+ const target = sessionRenameTarget;
458
+ const title = sessionRenameDraft.trim();
459
+ if (target === undefined || title.length === 0 || sessionRenamePending)
460
+ return;
461
+ if (renameSession === undefined) {
462
+ setSessionRenameError('Session rename is unavailable; retry after reconnecting.');
463
+ return;
464
+ }
465
+ setSessionRenamePending(true);
466
+ setSessionRenameError(undefined);
467
+ try {
468
+ await renameSession(target.sessionId, title);
469
+ setSessionRenameTarget(undefined);
470
+ }
471
+ catch (error) {
472
+ setSessionRenameError(error instanceof Error ? error.message : String(error));
473
+ }
474
+ finally {
475
+ setSessionRenamePending(false);
476
+ }
477
+ };
478
+ const archiveWorktreeSession = async (sessionId) => {
479
+ if (archiveSession === undefined)
480
+ return;
481
+ await runMutation(() => archiveSession(sessionId));
482
+ };
483
+ const toggleWorkspace = (workspaceId) => {
484
+ setExpandedWorkspaces((current) => ({
485
+ ...current,
486
+ [workspaceId]: current[workspaceId] === false,
487
+ }));
488
+ };
489
+ const toggleMain = (workspaceId) => {
490
+ setExpandedMains((current) => ({
491
+ ...current,
492
+ [workspaceId]: current[workspaceId] === false,
493
+ }));
494
+ };
495
+ const toggleWorktree = (worktreeId) => {
496
+ setExpandedWorktrees((current) => ({
497
+ ...current,
498
+ [worktreeId]: current[worktreeId] === false,
499
+ }));
500
+ };
501
+ const openWorktreeCreator = (workspace) => {
502
+ const view = viewByWorkspace.get(workspace.workspaceId);
503
+ const baseBranch = selectDefaultBaseBranch(view?.branches ?? []);
504
+ const existingNames = [
505
+ ...(view?.branches ?? []).map((branch) => branch.name),
506
+ ...(view?.worktrees ?? []).map((worktree) => worktree.branch),
507
+ ];
508
+ setWorktreeModalWorkspaceId(workspace.workspaceId);
509
+ setSelectedBranch(baseBranch);
510
+ setNewBranch(createDefaultWorktreeName(existingNames));
511
+ setActionError(undefined);
512
+ };
513
+ const submitWorktree = async () => {
514
+ const worktreeName = newBranch.trim();
515
+ if (manager === undefined ||
516
+ modalWorkspace === undefined ||
517
+ selectedBranch.length === 0 ||
518
+ worktreeName.length === 0) {
519
+ return;
520
+ }
521
+ const input = {
522
+ workspaceId: modalWorkspace.workspaceId,
523
+ branch: selectedBranch,
524
+ newBranch: worktreeName,
525
+ };
526
+ setActionPending(true);
527
+ setActionError(undefined);
528
+ setPendingSessionBinding(undefined);
529
+ try {
530
+ const createdWorktree = await executeWorktreeAction(manager, {
531
+ type: 'createWorktree',
532
+ input,
533
+ });
534
+ if (createdWorktree === undefined) {
535
+ throw new Error('Worktree creation returned no Worktree record; retry the request.');
536
+ }
537
+ setWorktreeModalWorkspaceId(undefined);
538
+ if (createSessionCallback === undefined) {
539
+ await refresh();
540
+ setActionError({
541
+ code: 'SESSION_CREATE_UNAVAILABLE',
542
+ message: 'Worktree created, but Session creation is unavailable; retry after reconnecting.',
543
+ retryable: true,
544
+ });
545
+ return;
546
+ }
547
+ const sessionInput = {
548
+ workspaceId: createdWorktree.workspaceId,
549
+ worktreeId: createdWorktree.worktreeId,
550
+ cwd: createdWorktree.absolutePath,
551
+ };
552
+ try {
553
+ await createSessionCallback(sessionInput);
554
+ }
555
+ catch (error) {
556
+ if (error instanceof WorktreeSessionBindingError) {
557
+ setPendingSessionBinding({ ...sessionInput, sessionId: error.sessionId });
558
+ }
559
+ throw error;
560
+ }
561
+ await refresh();
562
+ }
563
+ catch (error) {
564
+ setActionError(toWorktreeViewError(error));
565
+ }
566
+ finally {
567
+ setActionPending(false);
568
+ }
569
+ };
570
+ const createSession = async (input) => {
571
+ if (createSessionCallback === undefined) {
572
+ setActionError({
573
+ code: 'SESSION_CREATE_UNAVAILABLE',
574
+ message: 'Session creation is unavailable; retry after reconnecting.',
575
+ retryable: true,
576
+ });
577
+ return;
578
+ }
579
+ setActionPending(true);
580
+ setActionError(undefined);
581
+ setPendingSessionBinding(undefined);
582
+ try {
583
+ await createSessionCallback(input);
584
+ await refresh();
585
+ }
586
+ catch (error) {
587
+ if (error instanceof WorktreeSessionBindingError) {
588
+ setPendingSessionBinding({ ...input, sessionId: error.sessionId });
589
+ }
590
+ setActionError(toWorktreeViewError(error));
591
+ }
592
+ finally {
593
+ setActionPending(false);
594
+ }
595
+ };
596
+ const retrySessionBinding = async () => {
597
+ if (manager === undefined || pendingSessionBinding === undefined)
598
+ return;
599
+ setActionPending(true);
600
+ setActionError(undefined);
601
+ try {
602
+ await manager.bindSession({
603
+ workspaceId: pendingSessionBinding.workspaceId,
604
+ worktreeId: pendingSessionBinding.worktreeId,
605
+ sessionId: pendingSessionBinding.sessionId,
606
+ });
607
+ const sessionId = pendingSessionBinding.sessionId;
608
+ setPendingSessionBinding(undefined);
609
+ ensureSessionWorkspace?.(pendingSessionBinding.workspaceId, sessionId);
610
+ await refresh();
611
+ openSession(sessionId);
612
+ }
613
+ catch (error) {
614
+ setActionError(toWorktreeViewError(error));
615
+ }
616
+ finally {
617
+ setActionPending(false);
618
+ }
619
+ };
620
+ const openWorkspaceSession = (workspaceId, sessionId) => {
621
+ ensureSessionWorkspace?.(workspaceId, sessionId);
622
+ openWorktreeSession({ open: openSession }, sessionId);
623
+ };
624
+ if (mode !== 'worktree')
625
+ return null;
626
+ const visibleWorkspaces = workspaces.items.filter((workspace) => workspaceMatches(workspace, viewByWorkspace.get(workspace.workspaceId), sessions, query));
627
+ return (_jsxs("aside", { ref: ref, className: styles.surface, "data-worktree-surface": true, "data-collapsed": collapsed || undefined, "aria-label": "Worktree navigation", style: {
628
+ width: `${width}px`,
629
+ ...(bounds.ready
630
+ ? { top: `${bounds.top}px`, height: `${bounds.height}px` }
631
+ : { height: '0px', visibility: 'hidden' }),
632
+ }, children: [_jsxs("div", { className: styles.wideContent, children: [_jsxs("header", { className: styles.header, children: [_jsx("span", { className: styles.title, children: "Worktrees" }), _jsx("button", { type: "button", className: styles.closeButton, "aria-label": "Exit Worktree mode", onClick: () => {
633
+ actions.setViewMode('workspace-session');
634
+ }, children: _jsx(IconCloseOutline16, {}) })] }), _jsxs("div", { className: styles.searchRow, children: [_jsx("span", { className: styles.searchIcon, "aria-hidden": "true", children: _jsx(IconSearchOutline16, {}) }), _jsx("input", { className: styles.searchInput, "aria-label": "Search Workspaces and Sessions", placeholder: "Search Workspaces and Sessions", value: searchQuery, onChange: (event) => {
635
+ setSearchQuery(event.currentTarget.value);
636
+ } }), _jsx("button", { type: "button", className: styles.iconButton, "aria-label": "Add Workspace", onClick: () => {
637
+ if (createWorkspace !== undefined)
638
+ void runMutation(createWorkspace);
639
+ }, children: _jsx(IconPlusOutline16, {}) })] }), _jsxs("div", { className: styles.content, tabIndex: 0, children: [actionError !== undefined && (_jsxs("div", { className: styles.error, role: "alert", "data-worktree-error": true, children: [_jsx("p", { className: styles.message, "data-error": "true", children: actionError.message }), pendingSessionBinding !== undefined && (_jsxs("div", { className: styles.recoveryActions, children: [_jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
640
+ void retrySessionBinding();
641
+ }, children: "Retry Binding" }), _jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
642
+ const sessionId = pendingSessionBinding.sessionId;
643
+ setPendingSessionBinding(undefined);
644
+ setActionError(undefined);
645
+ openSession(sessionId);
646
+ }, children: "Open Created Session" })] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
647
+ setActionError(undefined);
648
+ void refresh();
649
+ }, children: "Retry" }))] })), readState.status === 'loading' && _jsx("p", { className: styles.message, children: "Loading Workspaces\u2026" }), readState.status === 'error' && readState.error !== undefined ? (_jsxs("div", { className: styles.error, role: "alert", "data-worktree-error": true, children: [_jsx("p", { className: styles.message, "data-error": "true", children: readState.error.message }), _jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
650
+ void refresh();
651
+ }, children: "Retry" })] })) : readState.status === 'ready' ? (_jsx("div", { className: styles.workspaceList, children: visibleWorkspaces.length === 0 ? (_jsx("p", { className: styles.empty, children: "No matching Workspaces" })) : (visibleWorkspaces.map((workspace) => {
652
+ const view = viewByWorkspace.get(workspace.workspaceId);
653
+ const expanded = expandedWorkspaces[workspace.workspaceId] !== false;
654
+ const workspaceMatchesQuery = includesText(workspace.title, query);
655
+ const allWorkspaceSessionIds = filterArchivedSessionIds(workspaceSessionIds(workspaces, workspace.workspaceId, sessions.ids), archivedSessionIds);
656
+ const bindings = view?.bindings ?? [];
657
+ const boundSessionIds = new Set(bindings.map((binding) => binding.sessionId));
658
+ const mainSessionIds = unboundSessionIds(allWorkspaceSessionIds, [
659
+ ...boundSessionIds,
660
+ ]);
661
+ const visibleMainSessionIds = mainSessionIds.filter((sessionId) => workspaceMatchesQuery ||
662
+ includesText(sessionLabel(sessionId, sessions), query));
663
+ const mainExpanded = expandedMains[workspace.workspaceId] !== false;
664
+ const mainGroupKey = `main:${workspace.workspaceId}`;
665
+ const worktrees = view?.worktrees ?? [];
666
+ return (_jsxs("section", { className: styles.workspaceGroup, "data-workspace-id": workspace.workspaceId, children: [_jsx(WorktreeWorkspaceRow, { workspace: workspace, expanded: expanded, actionPending: actionPending, menuOpen: openWorkspaceMenuId === workspace.workspaceId, drag: {
667
+ active: workspaceDrag !== undefined,
668
+ marker: workspaceDrag?.over?.workspaceId === workspace.workspaceId
669
+ ? workspaceDrag.over.half
670
+ : null,
671
+ start: () => {
672
+ workspaceDropCommitted.current = false;
673
+ setWorkspaceDrag({ workspaceId: workspace.workspaceId, over: null });
674
+ },
675
+ hover: (half) => {
676
+ setWorkspaceDrag((current) => current === undefined
677
+ ? current
678
+ : {
679
+ ...current,
680
+ over: { workspaceId: workspace.workspaceId, half },
681
+ });
682
+ },
683
+ drop: (half) => {
684
+ if (workspaceDrag === undefined)
685
+ return;
686
+ commitWorkspaceDrag(workspaceDrag, {
687
+ workspaceId: workspace.workspaceId,
688
+ half,
689
+ });
690
+ },
691
+ end: () => {
692
+ if (workspaceDrag?.over !== null && workspaceDrag?.over !== undefined) {
693
+ commitWorkspaceDrag(workspaceDrag, workspaceDrag.over);
694
+ }
695
+ else {
696
+ setWorkspaceDrag(undefined);
697
+ }
698
+ workspaceDropCommitted.current = false;
699
+ },
700
+ }, onToggle: () => {
701
+ toggleWorkspace(workspace.workspaceId);
702
+ }, onCreateWorktree: () => {
703
+ openWorktreeCreator(workspace);
704
+ }, onRename: () => {
705
+ openWorkspaceRename(workspace);
706
+ }, onDelete: () => {
707
+ openWorkspaceDelete(workspace);
708
+ }, onMenuOpenChange: (open) => {
709
+ setOpenWorkspaceMenuId(open ? workspace.workspaceId : undefined);
710
+ } }), expanded && (_jsxs("div", { className: styles.treeChildren, children: [_jsx(WorktreeGroupRow, { kind: "main", label: "Main", expanded: mainExpanded, icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, onToggle: () => {
711
+ toggleMain(workspace.workspaceId);
712
+ }, onCreateSession: createMainSession === undefined
713
+ ? undefined
714
+ : () => {
715
+ createMainSession(workspace.workspaceId);
716
+ } }), mainExpanded && (_jsx(WorktreeSessionGroup, { groupKey: mainGroupKey, sessionIds: visibleMainSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[mainGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
717
+ setExpandedSessionGroups((current) => ({
718
+ ...current,
719
+ [mainGroupKey]: current[mainGroupKey] !== true,
720
+ }));
721
+ }, onStartDrag: (groupKey, sessionId) => {
722
+ sessionDropCommitted.current = false;
723
+ setSessionDrag({ groupKey, sessionId, over: null });
724
+ }, onHoverDrag: (sessionId, half) => {
725
+ setSessionDrag((current) => current === undefined
726
+ ? current
727
+ : { ...current, over: { sessionId, half } });
728
+ }, onClearDrag: () => {
729
+ setSessionDrag(undefined);
730
+ }, onFinishDrag: () => {
731
+ sessionDropCommitted.current = false;
732
+ }, onCommitDrag: commitSessionDrag, onOpen: (sessionId) => {
733
+ openWorktreeSession({ open: openSession }, sessionId);
734
+ }, onRename: renameSession === undefined ? undefined : openSessionRename, onFork: forkSession, onArchive: archiveSession === undefined ? undefined : archiveWorktreeSession })), worktrees.length === 0 && (_jsx("p", { className: styles.emptyNested, children: "No Worktrees" })), worktrees.map((record) => {
735
+ const worktreeSessionIds = filterArchivedSessionIds(bindingIdsFor(bindings, record.worktreeId).filter((sessionId) => sessions.ids.includes(sessionId)), archivedSessionIds);
736
+ const worktreeMatchesQuery = workspaceMatchesQuery ||
737
+ includesText(record.branch, query) ||
738
+ includesText(record.absolutePath, query);
739
+ if (query.length > 0 &&
740
+ !worktreeMatchesQuery &&
741
+ !worktreeSessionIds.some((sessionId) => includesText(sessionLabel(sessionId, sessions), query))) {
742
+ return null;
743
+ }
744
+ const visibleWorktreeSessionIds = worktreeSessionIds.filter((sessionId) => worktreeMatchesQuery ||
745
+ includesText(sessionLabel(sessionId, sessions), query));
746
+ const worktreeGroupKey = `worktree:${record.worktreeId}`;
747
+ const state = record.status === 'removed'
748
+ ? 'warning'
749
+ : record.health === 'repair'
750
+ ? 'error'
751
+ : 'done';
752
+ const stateLabel = record.status === 'removed'
753
+ ? 'Detached Worktree'
754
+ : record.health === 'repair'
755
+ ? 'Worktree needs repair'
756
+ : 'Active Worktree ready';
757
+ const worktreeExpanded = expandedWorktrees[record.worktreeId] !== false;
758
+ return (_jsxs("div", { className: styles.worktreeGroup, "data-worktree-id": record.worktreeId, children: [_jsx(WorktreeGroupRow, { kind: "worktree", label: record.branch, expanded: worktreeExpanded, icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, state: state, stateLabel: stateLabel, onToggle: () => {
759
+ toggleWorktree(record.worktreeId);
760
+ }, onCreateSession: record.status === 'active'
761
+ ? () => {
762
+ void createSession({
763
+ workspaceId: record.workspaceId,
764
+ worktreeId: record.worktreeId,
765
+ cwd: record.absolutePath,
766
+ });
767
+ }
768
+ : undefined }), worktreeExpanded && (_jsx(WorktreeSessionGroup, { groupKey: worktreeGroupKey, sessionIds: visibleWorktreeSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[worktreeGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
769
+ setExpandedSessionGroups((current) => ({
770
+ ...current,
771
+ [worktreeGroupKey]: current[worktreeGroupKey] !== true,
772
+ }));
773
+ }, onStartDrag: (groupKey, sessionId) => {
774
+ sessionDropCommitted.current = false;
775
+ setSessionDrag({ groupKey, sessionId, over: null });
776
+ }, onHoverDrag: (sessionId, half) => {
777
+ setSessionDrag((current) => current === undefined
778
+ ? current
779
+ : { ...current, over: { sessionId, half } });
780
+ }, onClearDrag: () => {
781
+ setSessionDrag(undefined);
782
+ }, onFinishDrag: () => {
783
+ sessionDropCommitted.current = false;
784
+ }, onCommitDrag: commitSessionDrag, onOpen: (sessionId) => {
785
+ openWorkspaceSession(workspace.workspaceId, sessionId);
786
+ }, onRename: renameSession === undefined ? undefined : openSessionRename, onFork: forkSession, onArchive: archiveSession === undefined
787
+ ? undefined
788
+ : archiveWorktreeSession }))] }, record.worktreeId));
789
+ })] }))] }, workspace.workspaceId));
790
+ })) })) : null] })] }), _jsx("div", { className: styles.railContent, children: _jsx("button", { type: "button", className: styles.railButton, "aria-label": "Exit Worktree mode", onClick: () => {
791
+ actions.setViewMode('workspace-session');
792
+ }, children: "WT" }) }), _jsxs(Modal, { open: sessionRenameTarget !== undefined, onClose: closeSessionRename, closeLabel: "Close", title: "Rename session", footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: sessionRenamePending, onClick: closeSessionRename, children: "Cancel" }), _jsx(Button, { variant: "primary", disabled: sessionRenamePending || sessionRenameDraft.trim().length === 0, onClick: () => {
793
+ void confirmSessionRename();
794
+ }, children: "Rename" })] })), children: [_jsx(Input, { className: styles.renameInput, value: sessionRenameDraft, "aria-label": "Session name", autoFocus: true, disabled: sessionRenamePending, onFocus: (event) => {
795
+ event.currentTarget.select();
796
+ }, onChange: (event) => {
797
+ setSessionRenameDraft(event.currentTarget.value);
798
+ setSessionRenameError(undefined);
799
+ }, onKeyDown: (event) => {
800
+ if (event.key === 'Enter' && !event.nativeEvent.isComposing) {
801
+ event.preventDefault();
802
+ void confirmSessionRename();
803
+ }
804
+ } }), sessionRenameError !== undefined && (_jsx("p", { className: styles.renameError, role: "alert", children: sessionRenameError }))] }), workspaceRenameTarget !== undefined && (_jsxs(Modal, { open: workspaceRenameTarget !== undefined, onClose: closeWorkspaceRename, closeLabel: "Close Rename Workspace dialog", title: "Rename Workspace", footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: workspaceRenamePending, onClick: closeWorkspaceRename, children: "Cancel" }), _jsx(Button, { variant: "primary", disabled: workspaceRenameBlocked, onClick: () => {
805
+ void confirmWorkspaceRename();
806
+ }, children: "Rename" })] })), children: [_jsx(Input, { className: styles.renameInput, value: workspaceRenameDraft, "aria-label": "Workspace name", autoFocus: true, disabled: workspaceRenamePending, onFocus: (event) => {
807
+ event.currentTarget.select();
808
+ }, onChange: (event) => {
809
+ setWorkspaceRenameDraft(event.currentTarget.value);
810
+ setWorkspaceRenameError(undefined);
811
+ }, onKeyDown: (event) => {
812
+ if (event.key === 'Enter' && !event.nativeEvent.isComposing) {
813
+ event.preventDefault();
814
+ void confirmWorkspaceRename();
815
+ }
816
+ } }), workspaceRenameDuplicate && (_jsx("p", { className: styles.renameError, role: "alert", children: "A Workspace with this name already exists." })), workspaceRenameError !== undefined && (_jsx("p", { className: styles.renameError, role: "alert", children: workspaceRenameError }))] })), workspaceDeleteTarget !== undefined && (_jsx(Modal, { open: workspaceDeleteTarget !== undefined, onClose: closeWorkspaceDelete, closeLabel: "Close Delete Workspace dialog", title: "Delete Workspace", description: `Delete ${workspaceDeleteTarget.title}? This removes only the DSH Workspace registration. The directory, Sessions, and Git Worktrees will be retained.`, footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: workspaceDeletePending, onClick: closeWorkspaceDelete, children: "Cancel" }), _jsx(Button, { variant: "outline", disabled: workspaceDeletePending, onClick: () => {
817
+ void confirmWorkspaceDelete();
818
+ }, children: "Delete" })] })), children: workspaceDeleteError !== undefined && (_jsx("p", { className: styles.renameError, role: "alert", children: workspaceDeleteError })) })), worktreeModalWorkspaceId !== undefined && modalWorkspace !== undefined && (_jsxs(Modal, { open: worktreeModalWorkspaceId !== undefined, onClose: () => {
819
+ if (actionPending)
820
+ return;
821
+ setWorktreeModalWorkspaceId(undefined);
822
+ }, closeLabel: "Close Create Worktree dialog", title: "New Worktree", description: `${modalWorkspace.title} · the Worktree path is managed by DSH.`, footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: actionPending, onClick: () => {
823
+ setWorktreeModalWorkspaceId(undefined);
824
+ }, children: "Cancel" }), _jsx(Button, { variant: "primary", disabled: actionPending ||
825
+ selectedBranch.length === 0 ||
826
+ newBranch.trim().length === 0, onClick: () => {
827
+ void submitWorktree();
828
+ }, children: "Create Worktree" })] })), children: [_jsxs("label", { className: styles.modalField, children: ["Base branch", _jsxs("select", { className: styles.actionSelect, "aria-label": "Worktree base branch", value: selectedBranch, disabled: actionPending, onChange: (event) => {
829
+ setSelectedBranch(event.currentTarget.value);
830
+ }, children: [_jsx("option", { value: "", children: "No local branch" }), (modalView?.branches ?? []).map((branch) => (_jsxs("option", { value: branch.name, children: [branch.name, branch.isCurrent ? ' (current)' : '', branch.checkedOut ? ' (checked out)' : ''] }, branch.name)))] })] }), _jsxs("label", { className: styles.modalField, children: ["Worktree name", _jsx(Input, { className: styles.renameInput, "aria-label": "Worktree name", value: newBranch, placeholder: "dsh/12345678", disabled: actionPending, onChange: (event) => {
831
+ setNewBranch(event.currentTarget.value);
832
+ } })] }), modalView?.branches.length === 0 && (_jsx("p", { className: styles.empty, children: "No local branches found in this Workspace." }))] }))] }));
833
+ }
834
+ //# sourceMappingURL=WorktreeSurface.js.map