@cerbur/clutch-dsh-worktree 0.1.6 → 0.1.8

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 (178) hide show
  1. package/README.md +135 -23
  2. package/README.zh.md +107 -13
  3. package/cordis.patch.yml +21 -0
  4. package/lib/client/WorktreeContext.d.ts.map +1 -1
  5. package/lib/client/WorktreeContext.js +2 -2
  6. package/lib/client/WorktreeContext.js.map +1 -1
  7. package/lib/client/WorktreeSurface.d.ts +2 -2
  8. package/lib/client/WorktreeSurface.d.ts.map +1 -1
  9. package/lib/client/WorktreeSurface.js +291 -70
  10. package/lib/client/WorktreeSurface.js.map +1 -1
  11. package/lib/client/entry.d.ts.map +1 -1
  12. package/lib/client/entry.js +84 -2
  13. package/lib/client/entry.js.map +1 -1
  14. package/lib/client/index.d.ts +3 -0
  15. package/lib/client/index.d.ts.map +1 -1
  16. package/lib/client/index.js +2 -0
  17. package/lib/client/index.js.map +1 -1
  18. package/lib/client/locales.d.ts +70 -0
  19. package/lib/client/locales.d.ts.map +1 -1
  20. package/lib/client/locales.js +70 -0
  21. package/lib/client/locales.js.map +1 -1
  22. package/lib/client/session-view.d.ts +34 -0
  23. package/lib/client/session-view.d.ts.map +1 -1
  24. package/lib/client/session-view.js +119 -1
  25. package/lib/client/session-view.js.map +1 -1
  26. package/lib/client/worktree-connection.d.ts +4 -2
  27. package/lib/client/worktree-connection.d.ts.map +1 -1
  28. package/lib/client/worktree-connection.js +4 -0
  29. package/lib/client/worktree-connection.js.map +1 -1
  30. package/lib/client/worktree-context.js +1 -1
  31. package/lib/client/worktree-context.js.map +1 -1
  32. package/lib/client/worktree-error-copy.d.ts +2 -0
  33. package/lib/client/worktree-error-copy.d.ts.map +1 -1
  34. package/lib/client/worktree-error-copy.js +24 -0
  35. package/lib/client/worktree-error-copy.js.map +1 -1
  36. package/lib/client/worktree-permission-icon.d.ts +7 -0
  37. package/lib/client/worktree-permission-icon.d.ts.map +1 -0
  38. package/lib/client/worktree-permission-icon.js +119 -0
  39. package/lib/client/worktree-permission-icon.js.map +1 -0
  40. package/lib/client/worktree-permission.d.ts +21 -0
  41. package/lib/client/worktree-permission.d.ts.map +1 -0
  42. package/lib/client/worktree-permission.js +70 -0
  43. package/lib/client/worktree-permission.js.map +1 -0
  44. package/lib/client/worktree-session-fork.d.ts +65 -0
  45. package/lib/client/worktree-session-fork.d.ts.map +1 -0
  46. package/lib/client/worktree-session-fork.js +174 -0
  47. package/lib/client/worktree-session-fork.js.map +1 -0
  48. package/lib/client/worktree-session-order.d.ts +36 -0
  49. package/lib/client/worktree-session-order.d.ts.map +1 -0
  50. package/lib/client/worktree-session-order.js +215 -0
  51. package/lib/client/worktree-session-order.js.map +1 -0
  52. package/lib/client/worktree-session.d.ts +34 -1
  53. package/lib/client/worktree-session.d.ts.map +1 -1
  54. package/lib/client/worktree-session.js +101 -3
  55. package/lib/client/worktree-session.js.map +1 -1
  56. package/lib/client/worktree-surface-rows.d.ts +4 -4
  57. package/lib/client/worktree-surface-rows.d.ts.map +1 -1
  58. package/lib/client/worktree-surface-rows.js +139 -47
  59. package/lib/client/worktree-surface-rows.js.map +1 -1
  60. package/lib/client/worktree-surface-selectors.d.ts +2 -0
  61. package/lib/client/worktree-surface-selectors.d.ts.map +1 -1
  62. package/lib/client/worktree-surface-selectors.js +8 -0
  63. package/lib/client/worktree-surface-selectors.js.map +1 -1
  64. package/lib/client/worktree-surface-types.d.ts +46 -3
  65. package/lib/client/worktree-surface-types.d.ts.map +1 -1
  66. package/lib/client/worktree-view-actions.d.ts +8 -3
  67. package/lib/client/worktree-view-actions.d.ts.map +1 -1
  68. package/lib/client/worktree-view-actions.js +28 -2
  69. package/lib/client/worktree-view-actions.js.map +1 -1
  70. package/lib/client/worktree-view-errors.d.ts +10 -0
  71. package/lib/client/worktree-view-errors.d.ts.map +1 -1
  72. package/lib/client/worktree-view-errors.js +22 -1
  73. package/lib/client/worktree-view-errors.js.map +1 -1
  74. package/lib/client.js +1515 -207
  75. package/lib/client.js.map +1 -1
  76. package/lib/contract/index.contract.js +2 -0
  77. package/lib/contract/index.contract.js.map +1 -1
  78. package/lib/contract/index.d.ts +11 -3
  79. package/lib/contract/index.d.ts.map +1 -1
  80. package/lib/contract/index.js +8 -0
  81. package/lib/contract/index.js.map +1 -1
  82. package/lib/contract/worktree-permission.d.ts +73 -0
  83. package/lib/contract/worktree-permission.d.ts.map +1 -0
  84. package/lib/contract/worktree-permission.js +32 -0
  85. package/lib/contract/worktree-permission.js.map +1 -0
  86. package/lib/host/dsh-read-adapter.d.ts +1 -0
  87. package/lib/host/dsh-read-adapter.d.ts.map +1 -1
  88. package/lib/host/dsh-read-adapter.js +6 -0
  89. package/lib/host/dsh-read-adapter.js.map +1 -1
  90. package/lib/host/index.d.ts +4 -0
  91. package/lib/host/index.d.ts.map +1 -1
  92. package/lib/host/index.js +2 -0
  93. package/lib/host/index.js.map +1 -1
  94. package/lib/host/remote.d.ts +2 -2
  95. package/lib/host/remote.d.ts.map +1 -1
  96. package/lib/host/remote.js +7 -1
  97. package/lib/host/remote.js.map +1 -1
  98. package/lib/host/service.d.ts +5 -1
  99. package/lib/host/service.d.ts.map +1 -1
  100. package/lib/host/service.js +42 -3
  101. package/lib/host/service.js.map +1 -1
  102. package/lib/host/worktree-permission-manager.d.ts +14 -0
  103. package/lib/host/worktree-permission-manager.d.ts.map +1 -0
  104. package/lib/host/worktree-permission-manager.js +113 -0
  105. package/lib/host/worktree-permission-manager.js.map +1 -0
  106. package/lib/host/worktree-permission.d.ts +38 -0
  107. package/lib/host/worktree-permission.d.ts.map +1 -0
  108. package/lib/host/worktree-permission.js +156 -0
  109. package/lib/host/worktree-permission.js.map +1 -0
  110. package/lib/index.d.ts +6 -4
  111. package/lib/index.d.ts.map +1 -1
  112. package/lib/index.js +3 -1
  113. package/lib/index.js.map +1 -1
  114. package/lib/manage/manager-context.d.ts +2 -0
  115. package/lib/manage/manager-context.d.ts.map +1 -1
  116. package/lib/manage/manager-worktrees.d.ts +5 -0
  117. package/lib/manage/manager-worktrees.d.ts.map +1 -1
  118. package/lib/manage/manager-worktrees.js +125 -37
  119. package/lib/manage/manager-worktrees.js.map +1 -1
  120. package/lib/manage/manager.d.ts +20 -0
  121. package/lib/manage/manager.d.ts.map +1 -1
  122. package/lib/manage/manager.js +91 -13
  123. package/lib/manage/manager.js.map +1 -1
  124. package/lib/manage/types.d.ts +9 -1
  125. package/lib/manage/types.d.ts.map +1 -1
  126. package/lib/manage/worktree-permission.d.ts +3 -0
  127. package/lib/manage/worktree-permission.d.ts.map +1 -0
  128. package/lib/manage/worktree-permission.js +3 -0
  129. package/lib/manage/worktree-permission.js.map +1 -0
  130. package/lib/provider/git.d.ts +37 -9
  131. package/lib/provider/git.d.ts.map +1 -1
  132. package/lib/provider/git.js +162 -61
  133. package/lib/provider/git.js.map +1 -1
  134. package/lib/provider/index.d.ts +5 -1
  135. package/lib/provider/index.d.ts.map +1 -1
  136. package/lib/provider/index.js +4 -0
  137. package/lib/provider/index.js.map +1 -1
  138. package/lib/provider/mutation-lock.d.ts +37 -0
  139. package/lib/provider/mutation-lock.d.ts.map +1 -0
  140. package/lib/provider/mutation-lock.js +270 -0
  141. package/lib/provider/mutation-lock.js.map +1 -0
  142. package/lib/provider/mutation-token.d.ts +8 -0
  143. package/lib/provider/mutation-token.d.ts.map +1 -0
  144. package/lib/provider/mutation-token.js +20 -0
  145. package/lib/provider/mutation-token.js.map +1 -0
  146. package/lib/provider/repository-fingerprint.d.ts +8 -0
  147. package/lib/provider/repository-fingerprint.d.ts.map +1 -0
  148. package/lib/provider/repository-fingerprint.js +13 -0
  149. package/lib/provider/repository-fingerprint.js.map +1 -0
  150. package/lib/provider/sidecar-persistence.d.ts +7 -3
  151. package/lib/provider/sidecar-persistence.d.ts.map +1 -1
  152. package/lib/provider/sidecar-persistence.js +118 -44
  153. package/lib/provider/sidecar-persistence.js.map +1 -1
  154. package/lib/provider/sidecar-schema.d.ts +1 -1
  155. package/lib/provider/sidecar-schema.d.ts.map +1 -1
  156. package/lib/provider/sidecar-schema.js +140 -8
  157. package/lib/provider/sidecar-schema.js.map +1 -1
  158. package/lib/provider/sidecar.d.ts +3 -1
  159. package/lib/provider/sidecar.d.ts.map +1 -1
  160. package/lib/provider/sidecar.js +6 -1
  161. package/lib/provider/sidecar.js.map +1 -1
  162. package/lib/provider/subprocess.d.ts +37 -0
  163. package/lib/provider/subprocess.d.ts.map +1 -0
  164. package/lib/provider/subprocess.js +319 -0
  165. package/lib/provider/subprocess.js.map +1 -0
  166. package/lib/provider/transaction.d.ts +65 -0
  167. package/lib/provider/transaction.d.ts.map +1 -0
  168. package/lib/provider/transaction.js +1011 -0
  169. package/lib/provider/transaction.js.map +1 -0
  170. package/lib/provider/types.d.ts +98 -7
  171. package/lib/provider/types.d.ts.map +1 -1
  172. package/lib/provider/types.js +1 -1
  173. package/lib/provider/types.js.map +1 -1
  174. package/lib/typert.host.js +133 -30
  175. package/lib/typert.remote-client.d.ts +7 -3
  176. package/lib/typert.remote-client.d.ts.map +1 -1
  177. package/lib/typert.remote-client.js +133 -30
  178. package/package.json +3 -1
@@ -1,18 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
3
- import { IconBranchOutline16, IconCloseOutline16, IconPlusOutline16, IconSearchOutline16, } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { IconBranchOutline16, IconCloseOutline16, IconProjectAddOutline16, IconSearchOutline16, RiskConfirmation, } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { openWorktreeSession } from './navigation.js';
5
5
  import { useSidebarOverlayGeometry } from './sidebar-overlay-geometry.js';
6
6
  import { isMainExpanded, isWorkspaceExpanded, isWorktreeExpanded, } from './worktree-expand-state.js';
7
7
  import { effectiveViewMode, unboundSessionIds, workspaceSessionIds } from './view-mode.js';
8
- import { formatWorktreeViewError } from './worktree-error-copy.js';
9
- import { filterVisibleSessionIds, sessionMatchesQuery, } from './session-view.js';
8
+ import { formatWorktreePermissionNotice, formatWorktreeViewError, } from './worktree-error-copy.js';
9
+ import { deriveSessionPresentationIndex, filterVisibleSessionIds, hasOngoingSession, sessionMatchesQuery, } from './session-view.js';
10
+ import { nextSessionOrderAccount, reorderSessionIds, } from './worktree-session-order.js';
10
11
  import { retryWorktreeSessionBinding } from './worktree-session.js';
11
12
  import { WorktreeCreateDialog, WorktreeRemovalDialog, WorktreeSessionRenameDialog, WorktreeWorkspaceDeleteDialog, WorktreeWorkspaceRenameDialog, } from './worktree-surface-dialogs.js';
12
- import { bindingIdsFor, clearSessionGroupExpansion, includesText, isCompleteWorktreeWorkspaceSnapshot, currentSessionRevealKeys, resolveCurrentSessionLocation, workspaceMatches, } from './worktree-surface-selectors.js';
13
+ import { bindingIdsFor, clearSessionGroupExpansion, includesText, isCompleteWorktreeWorkspaceSnapshot, currentSessionRevealKeys, isSessionGroupAutoExpanded, resolveCurrentSessionLocation, workspaceMatches, } from './worktree-surface-selectors.js';
13
14
  import { scrollCurrentSessionIntoView } from './worktree-session-position.js';
14
15
  import { WorktreeGroupRow, WorktreeSessionGroup, WorktreeWorkspaceRow, } from './worktree-surface-rows.js';
15
- import { executeWorktreeAction, createDefaultWorktreeName, createWorktreeModalViewLoader, createWorktreeRefreshGuard, filterArchivedSessionIds, loadWorktreeViews, mergeWorktreeView, reconcileBaseBranchSelection, resolveWorktreeMove, selectDefaultBaseBranch, stableWorkspaceIds, toRetryableWorktreeOrderError, toWorktreeViewError, WorktreeSessionBindingError, } from './worktree-view.js';
16
+ import { executeWorktreeAction, createDefaultWorktreeName, createNumberedWorktreeName, createWorktreeModalViewLoader, createWorktreeRefreshGuard, filterArchivedSessionIds, loadWorktreeViews, mergeWorktreeView, reconcileBaseBranchSelection, resolveWorktreeMove, selectDefaultBaseBranch, stableWorkspaceIds, toRetryableWorktreeOrderError, toWorktreeViewError, WorktreeSessionBindingError, WorktreeSessionPermissionError, } from './worktree-view.js';
16
17
  import styles from './worktree.css';
17
18
  function toNativeWorktreeViewError(error) {
18
19
  const viewError = toWorktreeViewError(error);
@@ -21,6 +22,30 @@ function toNativeWorktreeViewError(error) {
21
22
  return { ...viewError, message: String(error) };
22
23
  }
23
24
  const EMPTY_READ_STATE = { status: 'idle', views: [] };
25
+ const EMPTY_PERMISSION_NOTICE = undefined;
26
+ const EMPTY_PERMISSION_NOTICE_SUBSCRIBE = () => () => { };
27
+ const EMPTY_PERMISSION_NOTICE_SNAPSHOT = () => EMPTY_PERMISSION_NOTICE;
28
+ const EMPTY_FULL_ACCESS_CONFIRMATION_SUBSCRIBE = () => () => { };
29
+ const EMPTY_FULL_ACCESS_CONFIRMATION_SNAPSHOT = () => undefined;
30
+ const EMPTY_FORK_RECOVERY_SNAPSHOT = { revision: 0, pending: [] };
31
+ const EMPTY_FORK_RECOVERY_STORE = {
32
+ getSnapshot: () => EMPTY_FORK_RECOVERY_SNAPSHOT,
33
+ subscribe: () => () => { },
34
+ };
35
+ function updatedAtById(sessionIds, sessions) {
36
+ return Object.fromEntries(sessionIds.map((sessionId) => [sessionId, sessions.byId[sessionId]?.updatedAt]));
37
+ }
38
+ function forkRecoveryError(recovery) {
39
+ const error = toNativeWorktreeViewError(recovery.error);
40
+ return {
41
+ ...error,
42
+ code: error.code === 'WORKTREE_VIEW_FAILED' ? 'SESSION_BINDING_FAILED' : error.code,
43
+ details: {
44
+ ...(error.details ?? {}),
45
+ sessionId: recovery.childSessionId,
46
+ },
47
+ };
48
+ }
24
49
  function useStableWorkspaceIds(workspaces) {
25
50
  const next = workspaces.map((workspace) => workspace.workspaceId);
26
51
  const previousRef = useRef([]);
@@ -33,15 +58,33 @@ function useStableWorkspaceIds(workspaces) {
33
58
  * Peer Worktree navigation surface. It keeps the original DSH Workspace mode
34
59
  * intact and renders a browser-local Workspace → Worktree → Session projection.
35
60
  */
36
- export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, t, available, expandState, manager, invalidateWorktreeContext, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, insertWorktreeBefore, renameSession, forkSession, archiveSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
61
+ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, t, available, expandState, sessionOrder, manager, permission, confirmFullAccess, fullAccessConfirmation, onPermissionResult, onPermissionNotice, permissionNotice, invalidateWorktreeContext, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, insertWorktreeBefore, renameSession, forkSession, archiveSession, forkRecovery, retryForkSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
37
62
  const preferredMode = useStore((state) => state.viewMode);
38
63
  const mode = effectiveViewMode(preferredMode, available && manager !== undefined);
39
64
  const sessions = useSessions((state) => state);
40
65
  const workspaces = useWorkspaces((state) => state);
66
+ const forkRecoveryStore = forkRecovery ?? EMPTY_FORK_RECOVERY_STORE;
67
+ const forkRecoverySnapshot = useSyncExternalStore(forkRecoveryStore.subscribe, forkRecoveryStore.getSnapshot, forkRecoveryStore.getSnapshot);
41
68
  const currentSessionId = sessions.current;
42
69
  const workspaceIds = useStableWorkspaceIds(workspaces.items);
43
70
  const [readState, setReadState] = useState(EMPTY_READ_STATE);
44
71
  const expandSnapshot = useSyncExternalStore(expandState.subscribe, expandState.getSnapshot, expandState.getSnapshot);
72
+ const permissionNoticeSnapshot = useSyncExternalStore(permissionNotice?.subscribe ?? EMPTY_PERMISSION_NOTICE_SUBSCRIBE, permissionNotice?.getSnapshot ?? EMPTY_PERMISSION_NOTICE_SNAPSHOT, permissionNotice?.getSnapshot ?? EMPTY_PERMISSION_NOTICE_SNAPSHOT);
73
+ const fullAccessConfirmationSnapshot = useSyncExternalStore(fullAccessConfirmation?.subscribe ?? EMPTY_FULL_ACCESS_CONFIRMATION_SUBSCRIBE, fullAccessConfirmation?.getSnapshot ?? EMPTY_FULL_ACCESS_CONFIRMATION_SNAPSHOT, fullAccessConfirmation?.getSnapshot ?? EMPTY_FULL_ACCESS_CONFIRMATION_SNAPSHOT);
74
+ const fullAccessConfirmationKey = fullAccessConfirmationSnapshot === undefined
75
+ ? ''
76
+ : [
77
+ fullAccessConfirmationSnapshot.workspaceId,
78
+ fullAccessConfirmationSnapshot.worktreeId,
79
+ fullAccessConfirmationSnapshot.sessionId,
80
+ fullAccessConfirmationSnapshot.cwd,
81
+ ].join('\u0000');
82
+ const [fullAccessAcknowledged, setFullAccessAcknowledged] = useState(false);
83
+ useEffect(() => {
84
+ setFullAccessAcknowledged(false);
85
+ }, [fullAccessConfirmationKey]);
86
+ const sessionOrderSnapshot = useSyncExternalStore(sessionOrder.subscribe, sessionOrder.getSnapshot, sessionOrder.getSnapshot);
87
+ const sessionPresentations = useMemo(() => deriveSessionPresentationIndex(sessions), [sessions.byId]);
45
88
  const readStateRef = useRef(readState);
46
89
  readStateRef.current = readState;
47
90
  const [searchQuery, setSearchQuery] = useState('');
@@ -54,9 +97,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
54
97
  const [modalReadError, setModalReadError] = useState();
55
98
  const [modalReadLoading, setModalReadLoading] = useState(false);
56
99
  const [worktreeModalMode, setWorktreeModalMode] = useState('create');
57
- const [importCandidates, setImportCandidates] = useState({ status: 'idle', candidates: [] });
100
+ const [importCandidates, setImportCandidates] = useState({
101
+ status: 'idle',
102
+ candidates: [],
103
+ });
58
104
  const [selectedImportPath, setSelectedImportPath] = useState();
59
105
  const [openWorkspaceMenuId, setOpenWorkspaceMenuId] = useState();
106
+ const [openMainMenuId, setOpenMainMenuId] = useState();
60
107
  const [openWorktreeMenuId, setOpenWorktreeMenuId] = useState();
61
108
  const [worktreeRemoval, setWorktreeRemoval] = useState();
62
109
  const [selectedBranch, setSelectedBranch] = useState('');
@@ -64,6 +111,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
64
111
  const [pendingSessionBinding, setPendingSessionBinding] = useState();
65
112
  const [actionError, setActionError] = useState();
66
113
  const [actionPending, setActionPending] = useState(false);
114
+ const [forkRetryKey, setForkRetryKey] = useState();
67
115
  const [sessionRenameTarget, setSessionRenameTarget] = useState();
68
116
  const [sessionRenameDraft, setSessionRenameDraft] = useState('');
69
117
  const [sessionRenamePending, setSessionRenamePending] = useState(false);
@@ -83,6 +131,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
83
131
  const [worktreeDrag, setWorktreeDrag] = useState();
84
132
  const worktreeDropCommitted = useRef(false);
85
133
  const refreshGuard = useRef(createWorktreeRefreshGuard());
134
+ const forkRecoveryRevisionRef = useRef(forkRecoverySnapshot.revision);
86
135
  const modalReadLoader = useRef(createWorktreeModalViewLoader());
87
136
  const modalReadViewRef = useRef();
88
137
  const importCandidatesGuard = useRef(createWorktreeRefreshGuard());
@@ -119,9 +168,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
119
168
  throw error;
120
169
  setReadState({
121
170
  status: 'error',
122
- views: modalReadViewRef.current === undefined
123
- ? []
124
- : [modalReadViewRef.current],
171
+ views: modalReadViewRef.current === undefined ? [] : [modalReadViewRef.current],
125
172
  error: toWorktreeViewError(error),
126
173
  });
127
174
  });
@@ -164,6 +211,19 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
164
211
  setReadState(EMPTY_READ_STATE);
165
212
  }
166
213
  }, [mode, refresh]);
214
+ useEffect(() => {
215
+ if (forkRecoveryRevisionRef.current === forkRecoverySnapshot.revision)
216
+ return;
217
+ forkRecoveryRevisionRef.current = forkRecoverySnapshot.revision;
218
+ if (mode !== 'worktree' || manager === undefined)
219
+ return;
220
+ void refresh({
221
+ preserveCurrent: readStateRef.current.status === 'ready',
222
+ invalidateContext: false,
223
+ }).catch(() => {
224
+ // Recovery state remains visible; the next explicit retry can refresh it again.
225
+ });
226
+ }, [forkRecoverySnapshot.revision, manager, mode, refresh]);
167
227
  useEffect(() => {
168
228
  if (readState.status !== 'ready' || syncSessionWorkspaces === undefined)
169
229
  return;
@@ -173,6 +233,60 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
173
233
  }))));
174
234
  }, [readState.status, readState.views, syncSessionWorkspaces]);
175
235
  const viewByWorkspace = useMemo(() => new Map(readState.views.map((view) => [view.workspaceId, view])), [readState.views]);
236
+ const sessionOrderInputs = useMemo(() => {
237
+ if (readState.status !== 'ready')
238
+ return [];
239
+ const inputs = [];
240
+ for (const workspace of workspaces.items) {
241
+ const view = viewByWorkspace.get(workspace.workspaceId);
242
+ const allWorkspaceSessionIds = filterArchivedSessionIds(workspaceSessionIds(workspaces, workspace.workspaceId, sessions.ids), archivedSessionIds);
243
+ const bindings = view?.bindings ?? [];
244
+ const boundSessionIds = new Set(bindings.map((binding) => binding.sessionId));
245
+ const mainSessionIds = filterVisibleSessionIds(unboundSessionIds(allWorkspaceSessionIds, [...boundSessionIds]), sessions);
246
+ inputs.push({
247
+ accountKey: `main:${workspace.workspaceId}`,
248
+ sessionIds: mainSessionIds,
249
+ updatedAtById: updatedAtById(mainSessionIds, sessions),
250
+ });
251
+ for (const record of view?.worktrees ?? []) {
252
+ const worktreeSessionIds = filterVisibleSessionIds(filterArchivedSessionIds(bindingIdsFor(bindings, record.worktreeId).filter((sessionId) => sessions.ids.includes(sessionId)), archivedSessionIds), sessions);
253
+ inputs.push({
254
+ accountKey: `worktree:${record.worktreeId}`,
255
+ sessionIds: worktreeSessionIds,
256
+ updatedAtById: updatedAtById(worktreeSessionIds, sessions),
257
+ });
258
+ }
259
+ }
260
+ return inputs;
261
+ }, [
262
+ archivedSessionIds,
263
+ readState.status,
264
+ sessions.byId,
265
+ sessions.ids,
266
+ viewByWorkspace,
267
+ workspaces,
268
+ workspaces.items,
269
+ ]);
270
+ const orderedSessionIdsByAccount = useMemo(() => {
271
+ const ordered = new Map();
272
+ for (const input of sessionOrderInputs) {
273
+ const previous = sessionOrderSnapshot.accounts[input.accountKey];
274
+ ordered.set(input.accountKey, nextSessionOrderAccount({
275
+ baseIds: input.sessionIds,
276
+ updatedAtById: input.updatedAtById,
277
+ previous,
278
+ }).order);
279
+ }
280
+ return ordered;
281
+ }, [sessionOrderInputs, sessionOrderSnapshot]);
282
+ useEffect(() => {
283
+ if (mode !== 'worktree' || readState.status !== 'ready')
284
+ return;
285
+ for (const input of sessionOrderInputs) {
286
+ sessionOrder.actions.reconcile(input.accountKey, input.sessionIds, input.updatedAtById);
287
+ }
288
+ sessionOrder.actions.retain(sessionOrderInputs.map((input) => input.accountKey));
289
+ }, [mode, readState.status, sessionOrder, sessionOrderInputs]);
176
290
  const currentSessionLocation = useMemo(() => readState.status === 'ready'
177
291
  ? resolveCurrentSessionLocation(currentSessionId, workspaces.items, readState.views)
178
292
  : undefined, [currentSessionId, readState.status, readState.views, workspaces.items]);
@@ -238,11 +352,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
238
352
  const modalSetupStatus = modalView === undefined
239
353
  ? undefined
240
354
  : modalReadiness?.status === 'ready'
241
- ? modalView.branches.length === 0 ? 'noLocalBranch' : undefined
355
+ ? modalView.branches.length === 0
356
+ ? 'noLocalBranch'
357
+ : undefined
242
358
  : modalReadiness?.status;
243
- const modalCanCreate = !modalReadLoading &&
244
- modalSetupStatus === undefined &&
245
- modalReadiness?.status === 'ready';
359
+ const modalCanCreate = !modalReadLoading && modalSetupStatus === undefined && modalReadiness?.status === 'ready';
246
360
  useEffect(() => {
247
361
  if (worktreeModalWorkspaceId === undefined || modalView === undefined)
248
362
  return;
@@ -388,9 +502,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
388
502
  const sourceIndex = rows.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
389
503
  if (targetIndex === -1 || sourceIndex === -1)
390
504
  return;
391
- const beforeWorkspaceId = over.half === 'before'
392
- ? over.workspaceId
393
- : rows[targetIndex + 1]?.workspaceId;
505
+ const beforeWorkspaceId = over.half === 'before' ? over.workspaceId : rows[targetIndex + 1]?.workspaceId;
394
506
  const anchorIndex = beforeWorkspaceId === undefined
395
507
  ? rows.length
396
508
  : rows.findIndex((workspace) => workspace.workspaceId === beforeWorkspaceId);
@@ -421,12 +533,8 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
421
533
  const sourceIndex = sessionIds.indexOf(activeDrag.sessionId);
422
534
  if (targetIndex === -1 || sourceIndex === -1)
423
535
  return;
424
- const beforeSessionId = over.half === 'before'
425
- ? over.sessionId
426
- : sessionIds[targetIndex + 1];
427
- const anchorIndex = beforeSessionId === undefined
428
- ? sessionIds.length
429
- : sessionIds.indexOf(beforeSessionId);
536
+ const beforeSessionId = over.half === 'before' ? over.sessionId : sessionIds[targetIndex + 1];
537
+ const anchorIndex = beforeSessionId === undefined ? sessionIds.length : sessionIds.indexOf(beforeSessionId);
430
538
  if (beforeSessionId === activeDrag.sessionId ||
431
539
  anchorIndex === sourceIndex ||
432
540
  anchorIndex === sourceIndex + 1) {
@@ -441,7 +549,12 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
441
549
  return;
442
550
  }
443
551
  setActionError(undefined);
444
- void insertSessionBefore(workspaceId, activeDrag.sessionId, beforeSessionId).catch((error) => {
552
+ const currentOrder = sessionOrderSnapshot.accounts[activeDrag.groupKey]?.order ?? sessionIds;
553
+ void insertSessionBefore(workspaceId, activeDrag.sessionId, beforeSessionId)
554
+ .then(() => {
555
+ sessionOrder.actions.setOrder(activeDrag.groupKey, reorderSessionIds(currentOrder, activeDrag.sessionId, over.sessionId, over.half));
556
+ })
557
+ .catch((error) => {
445
558
  setActionError(toWorktreeViewError(error));
446
559
  });
447
560
  };
@@ -582,8 +695,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
582
695
  if (visuallyExpanded)
583
696
  clearSessionGroups(['worktree:' + worktreeId]);
584
697
  };
585
- const toggleSessionGroup = (groupKey) => {
586
- const autoExpanded = isCurrentSessionReveal('session-group:' + groupKey);
698
+ const toggleSessionGroup = (groupKey, autoExpanded) => {
587
699
  const transientExpanded = expandedSessionGroups[groupKey] === true;
588
700
  if (autoExpanded) {
589
701
  suppressCurrentSessionReveal('session-group:' + groupKey);
@@ -609,7 +721,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
609
721
  setModalReadError(undefined);
610
722
  setModalReadLoading(false);
611
723
  };
612
- const openWorktreeCreator = (workspace) => {
724
+ const openWorktreeCreator = (workspace, defaults = {}) => {
613
725
  const view = viewByWorkspace.get(workspace.workspaceId);
614
726
  modalReadLoader.current.invalidate();
615
727
  modalReadViewRef.current = undefined;
@@ -622,13 +734,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
622
734
  setModalReadError(undefined);
623
735
  setModalReadLoading(false);
624
736
  if (view === undefined) {
625
- setSelectedBranch('');
626
- setNewBranch('');
737
+ setSelectedBranch(defaults.baseBranch ?? '');
738
+ setNewBranch(defaults.newBranch ?? '');
627
739
  loadModalWorktreeView(workspace.workspaceId);
628
740
  return;
629
741
  }
630
- setSelectedBranch(selectDefaultBaseBranch(view.branches));
631
- setNewBranch(createDefaultWorktreeName([
742
+ setSelectedBranch(defaults.baseBranch ?? selectDefaultBaseBranch(view.branches));
743
+ setNewBranch(defaults.newBranch ?? createDefaultWorktreeName([
632
744
  ...view.branches.map((branch) => branch.name),
633
745
  ...view.worktrees.map((worktree) => worktree.branch),
634
746
  ]));
@@ -665,6 +777,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
665
777
  if (error instanceof WorktreeSessionBindingError && error.retryable) {
666
778
  setPendingSessionBinding({ ...sessionInput, sessionId: error.sessionId });
667
779
  }
780
+ if (error instanceof WorktreeSessionPermissionError && error.retryable) {
781
+ setPendingSessionBinding({
782
+ ...sessionInput,
783
+ sessionId: error.sessionId,
784
+ permissionRequired: true,
785
+ });
786
+ }
668
787
  throw error;
669
788
  }
670
789
  await refresh({ preserveCurrent: true });
@@ -736,6 +855,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
736
855
  if (error instanceof WorktreeSessionBindingError && error.retryable) {
737
856
  setPendingSessionBinding({ ...input, sessionId: error.sessionId });
738
857
  }
858
+ if (error instanceof WorktreeSessionPermissionError && error.retryable) {
859
+ setPendingSessionBinding({
860
+ ...input,
861
+ sessionId: error.sessionId,
862
+ permissionRequired: true,
863
+ });
864
+ }
739
865
  setActionError(toWorktreeViewError(error));
740
866
  }
741
867
  finally {
@@ -743,9 +869,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
743
869
  }
744
870
  };
745
871
  const retrySessionBinding = async () => {
746
- if (manager === undefined ||
747
- pendingSessionBinding === undefined ||
748
- pendingSessionArchived)
872
+ if (manager === undefined || pendingSessionBinding === undefined || pendingSessionArchived)
749
873
  return;
750
874
  const pending = pendingSessionBinding;
751
875
  setActionPending(true);
@@ -758,6 +882,9 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
758
882
  ensureSessionWorkspace: (workspaceId, sessionId) => {
759
883
  ensureSessionWorkspace?.(workspaceId, sessionId);
760
884
  },
885
+ permission,
886
+ confirmFullAccess,
887
+ onPermissionResult,
761
888
  openSession,
762
889
  });
763
890
  setPendingSessionBinding(undefined);
@@ -774,6 +901,20 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
774
901
  setActionPending(false);
775
902
  }
776
903
  };
904
+ const retryForkBinding = async (key) => {
905
+ if (retryForkSession === undefined || forkRetryKey !== undefined)
906
+ return;
907
+ setForkRetryKey(key);
908
+ try {
909
+ await retryForkSession(key);
910
+ }
911
+ catch (error) {
912
+ setActionError(toNativeWorktreeViewError(error));
913
+ }
914
+ finally {
915
+ setForkRetryKey(undefined);
916
+ }
917
+ };
777
918
  const openWorkspaceSession = (workspaceId, sessionId) => {
778
919
  ensureSessionWorkspace?.(workspaceId, sessionId);
779
920
  openWorktreeSession({ open: openSession }, sessionId);
@@ -793,14 +934,21 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
793
934
  } }), _jsx("button", { type: "button", className: styles.iconButton, "aria-label": t('workspace.add'), onClick: () => {
794
935
  if (createWorkspace !== undefined)
795
936
  void runMutation(createWorkspace);
796
- }, 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: formatWorktreeViewError(actionError, t) }), pendingSessionBinding !== undefined && (_jsxs("div", { className: styles.recoveryActions, children: [_jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending || pendingSessionArchived, onClick: () => {
937
+ }, children: _jsx(IconProjectAddOutline16, {}) })] }), _jsxs("div", { className: styles.content, tabIndex: 0, children: [permissionNoticeSnapshot !== undefined && (_jsx("div", { className: styles.notice, role: "status", "data-worktree-permission-notice": true, children: _jsx("p", { className: styles.message, children: formatWorktreePermissionNotice(permissionNoticeSnapshot.result, t) }) })), forkRecoverySnapshot.pending.map((recovery) => {
938
+ const recoveryViewError = forkRecoveryError(recovery);
939
+ return (_jsxs("div", { className: styles.error, role: "alert", "data-fork-recovery": recovery.key, children: [_jsx("p", { className: styles.message, "data-error": "true", children: formatWorktreeViewError(recoveryViewError, t) }), _jsxs("div", { className: styles.recoveryActions, children: [retryForkSession !== undefined && recoveryViewError.retryable && (_jsx("button", { type: "button", className: styles.actionButton, disabled: forkRetryKey !== undefined, onClick: () => {
940
+ void retryForkBinding(recovery.key);
941
+ }, children: t('action.retryBinding') })), _jsx("button", { type: "button", className: styles.actionButton, disabled: forkRetryKey !== undefined, onClick: () => {
942
+ openSession(recovery.childSessionId);
943
+ }, children: t('action.openCreatedSession') })] })] }, recovery.key));
944
+ }), actionError !== undefined && (_jsxs("div", { className: styles.error, role: "alert", "data-worktree-error": true, children: [_jsx("p", { className: styles.message, "data-error": "true", children: formatWorktreeViewError(actionError, t) }), pendingSessionBinding !== undefined && (_jsxs("div", { className: styles.recoveryActions, children: [_jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending || pendingSessionArchived, onClick: () => {
797
945
  void retrySessionBinding();
798
- }, children: t('action.retryBinding') }), _jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
946
+ }, children: t('action.retryBinding') }), !pendingSessionBinding.permissionRequired && (_jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
799
947
  const sessionId = pendingSessionBinding.sessionId;
800
948
  setPendingSessionBinding(undefined);
801
949
  setActionError(undefined);
802
950
  openSession(sessionId);
803
- }, children: t('action.openCreatedSession') })] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
951
+ }, children: t('action.openCreatedSession') }))] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
804
952
  setActionError(undefined);
805
953
  void refresh();
806
954
  }, children: t('action.retry') }))] })), readState.status === 'loading' && (_jsx("p", { className: styles.message, children: t('status.loading') })), 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: formatWorktreeViewError(readState.error, t) }), _jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
@@ -815,20 +963,30 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
815
963
  isCurrentSessionReveal('workspace:' + workspace.workspaceId);
816
964
  const workspaceMatchesQuery = includesText(workspace.title, query);
817
965
  const allWorkspaceSessionIds = filterArchivedSessionIds(workspaceSessionIds(workspaces, workspace.workspaceId, sessions.ids), archivedSessionIds);
966
+ const workspaceActivitySessionIds = filterVisibleSessionIds(allWorkspaceSessionIds, sessions);
967
+ const workspaceHasOngoingSession = hasOngoingSession(workspaceActivitySessionIds, sessionPresentations);
818
968
  const bindings = view?.bindings ?? [];
819
969
  const boundSessionIds = new Set(bindings.map((binding) => binding.sessionId));
820
970
  const mainSessionIds = filterVisibleSessionIds(unboundSessionIds(allWorkspaceSessionIds, [...boundSessionIds]), sessions);
971
+ const mainGroupKey = `main:${workspace.workspaceId}`;
972
+ const orderedMainSessionIds = orderedSessionIdsByAccount.get(mainGroupKey) ?? mainSessionIds;
821
973
  const visibleMainSessionIds = mainSessionIds.filter((sessionId) => workspaceMatchesQuery || sessionMatchesQuery(sessionId, sessions, query));
974
+ const orderedVisibleMainSessionIds = orderedMainSessionIds.filter((sessionId) => workspaceMatchesQuery || sessionMatchesQuery(sessionId, sessions, query));
822
975
  const mainExpanded = isMainExpanded(expandSnapshot, workspace.workspaceId) ||
823
976
  isCurrentSessionReveal('main:' + workspace.workspaceId);
824
- const mainGroupKey = `main:${workspace.workspaceId}`;
825
- const sessionIds = visibleMainSessionIds;
977
+ const sessionIds = orderedVisibleMainSessionIds.length === visibleMainSessionIds.length
978
+ ? orderedVisibleMainSessionIds
979
+ : visibleMainSessionIds;
980
+ const mainSessionGroupAutoExpanded = isSessionGroupAutoExpanded(sessionIds, currentSessionId, isCurrentSessionReveal('session-group:' + mainGroupKey));
826
981
  const mainSessionGroupExpanded = expandedSessionGroups[mainGroupKey] === true ||
827
- (sessionIds.length > 5 &&
828
- isCurrentSessionReveal('session-group:' + mainGroupKey));
982
+ mainSessionGroupAutoExpanded;
829
983
  const worktrees = view?.worktrees ?? [];
984
+ const workspaceWorktreeNames = [
985
+ ...(view?.branches.map((branch) => branch.name) ?? []),
986
+ ...(view?.worktrees.map((worktree) => worktree.branch) ?? []),
987
+ ];
830
988
  const sameWorkspaceWorktreeDrag = worktreeDrag?.workspaceId === workspace.workspaceId;
831
- return (_jsxs("section", { className: styles.workspaceGroup, "data-workspace-id": workspace.workspaceId, children: [_jsx(WorktreeWorkspaceRow, { t: t, workspace: workspace, expanded: expanded, actionPending: actionPending, menuOpen: openWorkspaceMenuId === workspace.workspaceId, drag: {
989
+ return (_jsxs("section", { className: styles.workspaceGroup, "data-workspace-id": workspace.workspaceId, children: [_jsx(WorktreeWorkspaceRow, { t: t, workspace: workspace, expanded: expanded, hasOngoingSession: workspaceHasOngoingSession, actionPending: actionPending, menuOpen: openWorkspaceMenuId === workspace.workspaceId, drag: {
832
990
  active: workspaceDrag !== undefined,
833
991
  marker: workspaceDrag?.over?.workspaceId === workspace.workspaceId
834
992
  ? workspaceDrag.over.half
@@ -872,14 +1030,32 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
872
1030
  openWorkspaceDelete(workspace);
873
1031
  }, onMenuOpenChange: (open) => {
874
1032
  setOpenWorkspaceMenuId(open ? workspace.workspaceId : undefined);
875
- } }), expanded && (_jsxs("div", { className: styles.treeChildren, children: [_jsx(WorktreeGroupRow, { t: t, kind: "main", label: mainLabel, expanded: mainExpanded, icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, onToggle: () => {
1033
+ } }), expanded && (_jsxs("div", { className: styles.treeChildren, children: [_jsx(WorktreeGroupRow, { t: t, kind: "main", label: mainLabel, expanded: mainExpanded, hasOngoingSession: hasOngoingSession(mainSessionIds, sessionPresentations), icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, onToggle: () => {
876
1034
  toggleMain(workspace.workspaceId);
1035
+ }, menu: {
1036
+ open: openMainMenuId === workspace.workspaceId,
1037
+ label: mainLabel,
1038
+ copyPath: workspace.path,
1039
+ showCreate: currentBranch !== undefined,
1040
+ showRemove: false,
1041
+ disabled: actionPending,
1042
+ onOpenChange: (open) => {
1043
+ setOpenMainMenuId(open ? workspace.workspaceId : undefined);
1044
+ },
1045
+ onCreateWorktree: currentBranch === undefined
1046
+ ? undefined
1047
+ : () => {
1048
+ openWorktreeCreator(workspace, {
1049
+ baseBranch: currentBranch,
1050
+ newBranch: createNumberedWorktreeName(currentBranch, workspaceWorktreeNames),
1051
+ });
1052
+ },
877
1053
  }, onCreateSession: createMainSession === undefined
878
1054
  ? undefined
879
1055
  : () => {
880
1056
  createMainSession(workspace.workspaceId);
881
- } }), mainExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: mainGroupKey, sessionIds: sessionIds, workspaceId: workspace.workspaceId, currentSessionId: currentSessionId, expanded: mainSessionGroupExpanded, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
882
- toggleSessionGroup(mainGroupKey);
1057
+ } }), mainExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: mainGroupKey, sessionIds: sessionIds, workspaceId: workspace.workspaceId, currentSessionId: currentSessionId, expanded: mainSessionGroupExpanded, actionPending: actionPending, sessions: sessions, sessionPresentations: sessionPresentations, dragState: sessionDrag, onToggleExpanded: () => {
1058
+ toggleSessionGroup(mainGroupKey, mainSessionGroupAutoExpanded);
883
1059
  }, onStartDrag: (groupKey, sessionId) => {
884
1060
  sessionDropCommitted.current = false;
885
1061
  setSessionDrag({ groupKey, sessionId, over: null });
@@ -903,27 +1079,37 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
903
1079
  !worktreeSessionIds.some((sessionId) => sessionMatchesQuery(sessionId, sessions, query))) {
904
1080
  return null;
905
1081
  }
906
- const visibleWorktreeSessionIds = worktreeSessionIds.filter((sessionId) => worktreeMatchesQuery || sessionMatchesQuery(sessionId, sessions, query));
1082
+ const visibleWorktreeSessionIds = worktreeSessionIds.filter((sessionId) => worktreeMatchesQuery ||
1083
+ sessionMatchesQuery(sessionId, sessions, query));
907
1084
  const worktreeGroupKey = `worktree:${record.worktreeId}`;
908
- const sessionIds = visibleWorktreeSessionIds;
1085
+ const orderedWorktreeSessionIds = orderedSessionIdsByAccount.get(worktreeGroupKey) ??
1086
+ worktreeSessionIds;
1087
+ const orderedVisibleWorktreeSessionIds = orderedWorktreeSessionIds.filter((sessionId) => worktreeMatchesQuery ||
1088
+ sessionMatchesQuery(sessionId, sessions, query));
1089
+ const sessionIds = orderedVisibleWorktreeSessionIds.length ===
1090
+ visibleWorktreeSessionIds.length
1091
+ ? orderedVisibleWorktreeSessionIds
1092
+ : visibleWorktreeSessionIds;
1093
+ const sessionGroupAutoExpanded = isSessionGroupAutoExpanded(sessionIds, currentSessionId, isCurrentSessionReveal('session-group:' + worktreeGroupKey));
909
1094
  const sessionGroupExpanded = expandedSessionGroups[worktreeGroupKey] === true ||
910
- (sessionIds.length > 5 &&
911
- isCurrentSessionReveal('session-group:' + worktreeGroupKey));
1095
+ sessionGroupAutoExpanded;
912
1096
  const state = record.status === 'removed'
913
1097
  ? 'warning'
914
- : record.health === 'repair'
1098
+ : record.health === 'repair' || record.health === 'recovery-needed'
915
1099
  ? 'error'
916
1100
  : 'done';
917
1101
  const stateLabel = record.status === 'removed'
918
1102
  ? t('worktree.detached')
919
- : record.health === 'repair'
920
- ? t('worktree.repair')
921
- : t('worktree.ready');
1103
+ : record.health === 'recovery-needed'
1104
+ ? t('worktree.recovery')
1105
+ : record.health === 'repair'
1106
+ ? t('worktree.repair')
1107
+ : t('worktree.ready');
922
1108
  const worktreeExpanded = isWorktreeExpanded(expandSnapshot, record.worktreeId) ||
923
1109
  isCurrentSessionReveal('worktree:' + record.worktreeId);
924
- return (_jsxs("div", { className: styles.worktreeGroup, "data-worktree-id": record.worktreeId, children: [_jsx(WorktreeGroupRow, { t: t, kind: "worktree", label: record.branch, worktreeId: record.worktreeId, expanded: worktreeExpanded, icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, state: state, stateLabel: stateLabel, onToggle: () => {
1110
+ return (_jsxs("div", { className: styles.worktreeGroup, "data-worktree-id": record.worktreeId, children: [_jsx(WorktreeGroupRow, { t: t, kind: "worktree", label: record.branch, worktreeId: record.worktreeId, expanded: worktreeExpanded, hasOngoingSession: hasOngoingSession(worktreeSessionIds, sessionPresentations), icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, state: state, stateLabel: stateLabel, onToggle: () => {
925
1111
  toggleWorktree(record.worktreeId);
926
- }, onCreateSession: record.status === 'active' && record.health !== 'repair'
1112
+ }, onCreateSession: record.status === 'active' && record.health !== 'repair' && record.health !== 'recovery-needed'
927
1113
  ? () => {
928
1114
  void createSession({
929
1115
  workspaceId: record.workspaceId,
@@ -931,20 +1117,31 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
931
1117
  cwd: record.absolutePath,
932
1118
  });
933
1119
  }
934
- : undefined, menu: record.status === 'active'
935
- ? {
936
- open: openWorktreeMenuId === record.worktreeId,
937
- label: record.branch,
938
- disabled: actionPending,
939
- onOpenChange: (open) => {
940
- setOpenWorktreeMenuId(open ? record.worktreeId : undefined);
941
- },
942
- onRemove: () => {
1120
+ : undefined, menu: {
1121
+ open: openWorktreeMenuId === record.worktreeId,
1122
+ label: record.branch,
1123
+ copyPath: record.absolutePath,
1124
+ showCreate: record.status === 'active' && record.health !== 'repair' && record.health !== 'recovery-needed',
1125
+ showRemove: record.status === 'active' && record.health !== 'repair' && record.health !== 'recovery-needed',
1126
+ disabled: actionPending,
1127
+ onOpenChange: (open) => {
1128
+ setOpenWorktreeMenuId(open ? record.worktreeId : undefined);
1129
+ },
1130
+ onCreateWorktree: record.status === 'active' && record.health !== 'repair' && record.health !== 'recovery-needed'
1131
+ ? () => {
1132
+ openWorktreeCreator(workspace, {
1133
+ baseBranch: record.branch,
1134
+ newBranch: createNumberedWorktreeName(record.branch, workspaceWorktreeNames),
1135
+ });
1136
+ }
1137
+ : undefined,
1138
+ onRemove: record.status === 'active'
1139
+ ? () => {
943
1140
  setWorktreeRemoval(record);
944
1141
  setActionError(undefined);
945
- },
946
- }
947
- : undefined, drag: {
1142
+ }
1143
+ : undefined,
1144
+ }, drag: {
948
1145
  active: sameWorkspaceWorktreeDrag,
949
1146
  marker: worktreeDrag?.over?.worktreeId === record.worktreeId
950
1147
  ? worktreeDrag.over.half
@@ -975,8 +1172,8 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
975
1172
  setWorktreeDrag(undefined);
976
1173
  worktreeDropCommitted.current = false;
977
1174
  },
978
- } }), worktreeExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: worktreeGroupKey, sessionIds: sessionIds, workspaceId: workspace.workspaceId, currentSessionId: currentSessionId, expanded: sessionGroupExpanded, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
979
- toggleSessionGroup(worktreeGroupKey);
1175
+ } }), worktreeExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: worktreeGroupKey, sessionIds: sessionIds, workspaceId: workspace.workspaceId, currentSessionId: currentSessionId, expanded: sessionGroupExpanded, actionPending: actionPending, sessions: sessions, sessionPresentations: sessionPresentations, dragState: sessionDrag, onToggleExpanded: () => {
1176
+ toggleSessionGroup(worktreeGroupKey, sessionGroupAutoExpanded);
980
1177
  }, onStartDrag: (groupKey, sessionId) => {
981
1178
  sessionDropCommitted.current = false;
982
1179
  setSessionDrag({ groupKey, sessionId, over: null });
@@ -1017,17 +1214,41 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
1017
1214
  if (manager === undefined || worktreeRemoval === undefined)
1018
1215
  return;
1019
1216
  const target = worktreeRemoval;
1217
+ const mutationToken = target.mutationToken;
1218
+ if (mutationToken === undefined) {
1219
+ setActionError({
1220
+ code: 'WORKTREE_STATE_CONFLICT',
1221
+ message: '',
1222
+ retryable: true,
1223
+ details: {
1224
+ workspaceId: target.workspaceId,
1225
+ worktreeId: target.worktreeId,
1226
+ },
1227
+ });
1228
+ return;
1229
+ }
1020
1230
  void runMutation(async () => {
1021
1231
  await executeWorktreeAction(manager, {
1022
1232
  type: 'removeWorktree',
1023
1233
  input: {
1024
1234
  workspaceId: target.workspaceId,
1025
1235
  worktreeId: target.worktreeId,
1236
+ mutationToken,
1026
1237
  },
1027
- });
1238
+ }, permission, onPermissionNotice);
1028
1239
  await invalidateWorktreeContext?.(target.workspaceId);
1029
1240
  setWorktreeRemoval(undefined);
1030
1241
  });
1242
+ } }), _jsx(RiskConfirmation, { open: fullAccessConfirmationSnapshot !== undefined, title: t('permission.fullAccessTitle'), description: fullAccessConfirmationSnapshot === undefined
1243
+ ? ''
1244
+ : t('permission.fullAccessDescription', {
1245
+ cwd: fullAccessConfirmationSnapshot.cwd,
1246
+ }), acknowledgeLabel: t('permission.fullAccessAcknowledge'), cancelLabel: t('dialog.cancel'), confirmLabel: t('permission.fullAccessEnable'), acknowledged: fullAccessAcknowledged, onAcknowledgedChange: setFullAccessAcknowledged, onCancel: () => {
1247
+ setFullAccessAcknowledged(false);
1248
+ fullAccessConfirmation?.resolve(false);
1249
+ }, onConfirm: () => {
1250
+ setFullAccessAcknowledged(false);
1251
+ fullAccessConfirmation?.resolve(true);
1031
1252
  } })] }));
1032
1253
  }
1033
1254
  //# sourceMappingURL=WorktreeSurface.js.map