@cerbur/clutch-dsh-worktree 0.1.8 → 0.1.9

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 (84) hide show
  1. package/README.md +18 -23
  2. package/README.zh.md +17 -21
  3. package/lib/client/WorktreeContext.d.ts +1 -1
  4. package/lib/client/WorktreeContext.d.ts.map +1 -1
  5. package/lib/client/WorktreeContext.js +1 -1
  6. package/lib/client/WorktreeContext.js.map +1 -1
  7. package/lib/client/WorktreeHeroContext.d.ts.map +1 -1
  8. package/lib/client/WorktreeHeroContext.js +1 -1
  9. package/lib/client/WorktreeHeroContext.js.map +1 -1
  10. package/lib/client/WorktreeModeAction.d.ts.map +1 -1
  11. package/lib/client/WorktreeModeAction.js.map +1 -1
  12. package/lib/client/WorktreeSurface.d.ts +1 -1
  13. package/lib/client/WorktreeSurface.d.ts.map +1 -1
  14. package/lib/client/WorktreeSurface.js +221 -45
  15. package/lib/client/WorktreeSurface.js.map +1 -1
  16. package/lib/client/dsh-rc1-slot-contract.d.ts +43 -0
  17. package/lib/client/dsh-rc1-slot-contract.d.ts.map +1 -0
  18. package/lib/client/dsh-rc1-slot-contract.js +2 -0
  19. package/lib/client/dsh-rc1-slot-contract.js.map +1 -0
  20. package/lib/client/dsh-slot-contract.d.ts +43 -0
  21. package/lib/client/dsh-slot-contract.d.ts.map +1 -0
  22. package/lib/client/dsh-slot-contract.js +2 -0
  23. package/lib/client/dsh-slot-contract.js.map +1 -0
  24. package/lib/client/entry.d.ts +2 -2
  25. package/lib/client/entry.d.ts.map +1 -1
  26. package/lib/client/entry.js +144 -26
  27. package/lib/client/entry.js.map +1 -1
  28. package/lib/client/view-mode-store.d.ts +1 -1
  29. package/lib/client/view-mode-store.d.ts.map +1 -1
  30. package/lib/client/view-mode-store.js +1 -1
  31. package/lib/client/view-mode-store.js.map +1 -1
  32. package/lib/client/view-mode.d.ts +20 -6
  33. package/lib/client/view-mode.d.ts.map +1 -1
  34. package/lib/client/view-mode.js +35 -10
  35. package/lib/client/view-mode.js.map +1 -1
  36. package/lib/client/virtual-workspace-membership.d.ts +12 -10
  37. package/lib/client/virtual-workspace-membership.d.ts.map +1 -1
  38. package/lib/client/virtual-workspace-membership.js +82 -57
  39. package/lib/client/virtual-workspace-membership.js.map +1 -1
  40. package/lib/client/worktree-context-store.d.ts +7 -15
  41. package/lib/client/worktree-context-store.d.ts.map +1 -1
  42. package/lib/client/worktree-context-store.js +20 -13
  43. package/lib/client/worktree-context-store.js.map +1 -1
  44. package/lib/client/worktree-expand-state.d.ts +5 -4
  45. package/lib/client/worktree-expand-state.d.ts.map +1 -1
  46. package/lib/client/worktree-expand-state.js.map +1 -1
  47. package/lib/client/worktree-session-fork.d.ts +18 -3
  48. package/lib/client/worktree-session-fork.d.ts.map +1 -1
  49. package/lib/client/worktree-session-fork.js +125 -36
  50. package/lib/client/worktree-session-fork.js.map +1 -1
  51. package/lib/client/worktree-session-order.d.ts +2 -2
  52. package/lib/client/worktree-session-order.d.ts.map +1 -1
  53. package/lib/client/worktree-surface-rows.d.ts.map +1 -1
  54. package/lib/client/worktree-surface-rows.js +1 -1
  55. package/lib/client/worktree-surface-rows.js.map +1 -1
  56. package/lib/client/worktree-surface-types.d.ts +19 -1
  57. package/lib/client/worktree-surface-types.d.ts.map +1 -1
  58. package/lib/client/worktree-view-read.d.ts +18 -8
  59. package/lib/client/worktree-view-read.d.ts.map +1 -1
  60. package/lib/client/worktree-view-read.js +75 -10
  61. package/lib/client/worktree-view-read.js.map +1 -1
  62. package/lib/client.js +1359 -169
  63. package/lib/client.js.map +1 -1
  64. package/lib/host/worktree-permission.d.ts +3 -2
  65. package/lib/host/worktree-permission.d.ts.map +1 -1
  66. package/lib/host/worktree-permission.js +29 -5
  67. package/lib/host/worktree-permission.js.map +1 -1
  68. package/lib/manage/manager-worktrees.d.ts.map +1 -1
  69. package/lib/manage/manager-worktrees.js +5 -2
  70. package/lib/manage/manager-worktrees.js.map +1 -1
  71. package/lib/manage/manager.d.ts.map +1 -1
  72. package/lib/manage/manager.js +2 -0
  73. package/lib/manage/manager.js.map +1 -1
  74. package/lib/provider/git.d.ts.map +1 -1
  75. package/lib/provider/git.js +4 -0
  76. package/lib/provider/git.js.map +1 -1
  77. package/lib/provider/sidecar.js +1 -1
  78. package/lib/provider/sidecar.js.map +1 -1
  79. package/lib/provider/subprocess.d.ts.map +1 -1
  80. package/lib/provider/subprocess.js +8 -0
  81. package/lib/provider/subprocess.js.map +1 -1
  82. package/lib/provider/transaction.js +2 -2
  83. package/lib/provider/transaction.js.map +1 -1
  84. package/package.json +48 -34
@@ -13,7 +13,7 @@ import { WorktreeCreateDialog, WorktreeRemovalDialog, WorktreeSessionRenameDialo
13
13
  import { bindingIdsFor, clearSessionGroupExpansion, includesText, isCompleteWorktreeWorkspaceSnapshot, currentSessionRevealKeys, isSessionGroupAutoExpanded, resolveCurrentSessionLocation, workspaceMatches, } from './worktree-surface-selectors.js';
14
14
  import { scrollCurrentSessionIntoView } from './worktree-session-position.js';
15
15
  import { WorktreeGroupRow, WorktreeSessionGroup, WorktreeWorkspaceRow, } from './worktree-surface-rows.js';
16
- import { executeWorktreeAction, createDefaultWorktreeName, createNumberedWorktreeName, createWorktreeModalViewLoader, createWorktreeRefreshGuard, filterArchivedSessionIds, loadWorktreeViews, mergeWorktreeView, reconcileBaseBranchSelection, resolveWorktreeMove, selectDefaultBaseBranch, stableWorkspaceIds, toRetryableWorktreeOrderError, toWorktreeViewError, WorktreeSessionBindingError, WorktreeSessionPermissionError, } from './worktree-view.js';
16
+ import { executeWorktreeAction, createDefaultWorktreeName, createNumberedWorktreeName, createWorktreeModalViewLoader, createWorktreeRefreshGuard, filterArchivedSessionIds, mergeWorktreeView, mergeWorktreeViews, reconcileBaseBranchSelection, resolveWorktreeMove, selectDefaultBaseBranch, stableWorkspaceIds, toRetryableWorktreeOrderError, toWorktreeViewError, WorktreeSessionBindingError, WorktreeSessionPermissionError, } from './worktree-view.js';
17
17
  import styles from './worktree.css';
18
18
  function toNativeWorktreeViewError(error) {
19
19
  const viewError = toWorktreeViewError(error);
@@ -27,7 +27,11 @@ const EMPTY_PERMISSION_NOTICE_SUBSCRIBE = () => () => { };
27
27
  const EMPTY_PERMISSION_NOTICE_SNAPSHOT = () => EMPTY_PERMISSION_NOTICE;
28
28
  const EMPTY_FULL_ACCESS_CONFIRMATION_SUBSCRIBE = () => () => { };
29
29
  const EMPTY_FULL_ACCESS_CONFIRMATION_SNAPSHOT = () => undefined;
30
- const EMPTY_FORK_RECOVERY_SNAPSHOT = { revision: 0, pending: [] };
30
+ const EMPTY_FORK_RECOVERY_SNAPSHOT = {
31
+ revision: 0,
32
+ pending: [],
33
+ affectedWorkspaceIds: [],
34
+ };
31
35
  const EMPTY_FORK_RECOVERY_STORE = {
32
36
  getSnapshot: () => EMPTY_FORK_RECOVERY_SNAPSHOT,
33
37
  subscribe: () => () => { },
@@ -58,7 +62,7 @@ function useStableWorkspaceIds(workspaces) {
58
62
  * Peer Worktree navigation surface. It keeps the original DSH Workspace mode
59
63
  * intact and renders a browser-local Workspace → Worktree → Session projection.
60
64
  */
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, }) {
65
+ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, t, available, expandState, sessionOrder, manager, viewReader, permission, confirmFullAccess, fullAccessConfirmation, onPermissionResult, onPermissionNotice, permissionNotice, invalidateWorktreeContext, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, insertWorktreeBefore, renameSession, forkSession, archiveSession, forkRecovery, retryForkSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
62
66
  const preferredMode = useStore((state) => state.viewMode);
63
67
  const mode = effectiveViewMode(preferredMode, available && manager !== undefined);
64
68
  const sessions = useSessions((state) => state);
@@ -67,6 +71,8 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
67
71
  const forkRecoverySnapshot = useSyncExternalStore(forkRecoveryStore.subscribe, forkRecoveryStore.getSnapshot, forkRecoveryStore.getSnapshot);
68
72
  const currentSessionId = sessions.current;
69
73
  const workspaceIds = useStableWorkspaceIds(workspaces.items);
74
+ const workspaceIdsRef = useRef(workspaceIds);
75
+ workspaceIdsRef.current = workspaceIds;
70
76
  const [readState, setReadState] = useState(EMPTY_READ_STATE);
71
77
  const expandSnapshot = useSyncExternalStore(expandState.subscribe, expandState.getSnapshot, expandState.getSnapshot);
72
78
  const permissionNoticeSnapshot = useSyncExternalStore(permissionNotice?.subscribe ?? EMPTY_PERMISSION_NOTICE_SUBSCRIBE, permissionNotice?.getSnapshot ?? EMPTY_PERMISSION_NOTICE_SNAPSHOT, permissionNotice?.getSnapshot ?? EMPTY_PERMISSION_NOTICE_SNAPSHOT);
@@ -131,8 +137,9 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
131
137
  const [worktreeDrag, setWorktreeDrag] = useState();
132
138
  const worktreeDropCommitted = useRef(false);
133
139
  const refreshGuard = useRef(createWorktreeRefreshGuard());
140
+ const targetRefreshGuards = useRef(new Map());
134
141
  const forkRecoveryRevisionRef = useRef(forkRecoverySnapshot.revision);
135
- const modalReadLoader = useRef(createWorktreeModalViewLoader());
142
+ const modalReadLoader = useRef(createWorktreeModalViewLoader(viewReader));
136
143
  const modalReadViewRef = useRef();
137
144
  const importCandidatesGuard = useRef(createWorktreeRefreshGuard());
138
145
  const modalWorkspaceIdRef = useRef(worktreeModalWorkspaceId);
@@ -143,36 +150,131 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
143
150
  const archivedSessionIds = workspaces.archivedSessionIds ?? [];
144
151
  const pendingSessionArchived = pendingSessionBinding !== undefined &&
145
152
  archivedSessionIds.includes(pendingSessionBinding.sessionId);
153
+ const guardFor = (workspaceId) => {
154
+ const current = targetRefreshGuards.current.get(workspaceId);
155
+ if (current !== undefined)
156
+ return current;
157
+ const created = createWorktreeRefreshGuard();
158
+ targetRefreshGuards.current.set(workspaceId, created);
159
+ return created;
160
+ };
146
161
  const refresh = useCallback(async (options = {}) => {
147
162
  if (manager === undefined) {
148
163
  refreshGuard.current.invalidate();
164
+ for (const guard of targetRefreshGuards.current.values())
165
+ guard.invalidate();
149
166
  setReadState(EMPTY_READ_STATE);
150
167
  return;
151
168
  }
169
+ const scope = options.scope ?? { kind: 'global' };
152
170
  const preserveCurrent = options.preserveCurrent === true;
153
- if (!preserveCurrent) {
154
- setReadState({ status: 'loading', views: [] });
155
- }
156
- await refreshGuard.current.run(() => loadWorktreeViews(manager, workspaceIds, {
157
- invalidateContext: options.invalidateContext !== false,
158
- invalidateWorktreeContext,
159
- }), (views) => {
160
- setReadState({
161
- status: 'ready',
162
- views: modalReadViewRef.current === undefined
163
- ? views
164
- : mergeWorktreeView(views, modalReadViewRef.current),
165
- });
166
- }, (error) => {
167
- if (preserveCurrent)
168
- throw error;
169
- setReadState({
170
- status: 'error',
171
- views: modalReadViewRef.current === undefined ? [] : [modalReadViewRef.current],
172
- error: toWorktreeViewError(error),
171
+ const currentWorkspaceIds = workspaceIdsRef.current;
172
+ if (scope.kind === 'global') {
173
+ if (!preserveCurrent) {
174
+ setReadState({ status: 'loading', views: [] });
175
+ }
176
+ const selectedWorkspaceIds = [...currentWorkspaceIds];
177
+ for (const workspaceId of selectedWorkspaceIds) {
178
+ viewReader.invalidate(workspaceId);
179
+ }
180
+ const contextRefresh = options.invalidateContext === false
181
+ ? undefined
182
+ : invalidateWorktreeContext?.();
183
+ await refreshGuard.current.run(async () => {
184
+ const [views] = await Promise.all([
185
+ viewReader.readMany(selectedWorkspaceIds),
186
+ contextRefresh ?? Promise.resolve(),
187
+ ]);
188
+ return views;
189
+ }, (views) => {
190
+ setReadState((current) => {
191
+ const merged = mergeWorktreeViews(current.views, workspaceIdsRef.current, views);
192
+ return {
193
+ status: 'ready',
194
+ views: modalReadViewRef.current === undefined
195
+ ? merged
196
+ : mergeWorktreeView(merged, modalReadViewRef.current),
197
+ };
198
+ });
199
+ }, (error) => {
200
+ if (preserveCurrent)
201
+ throw error;
202
+ setReadState({
203
+ status: 'error',
204
+ views: modalReadViewRef.current === undefined ? [] : [modalReadViewRef.current],
205
+ error: toWorktreeViewError(error),
206
+ });
173
207
  });
174
- });
175
- }, [invalidateWorktreeContext, manager, workspaceIds]);
208
+ return;
209
+ }
210
+ const requestedWorkspaceIds = scope.kind === 'workspace'
211
+ ? [scope.workspaceId]
212
+ : scope.workspaceIds;
213
+ const selectedWorkspaceIds = [...new Set(requestedWorkspaceIds)].filter((workspaceId) => currentWorkspaceIds.includes(workspaceId));
214
+ if (selectedWorkspaceIds.length === 0)
215
+ return;
216
+ await Promise.allSettled(selectedWorkspaceIds.map(async (workspaceId) => {
217
+ const guard = guardFor(workspaceId);
218
+ viewReader.invalidate(workspaceId);
219
+ const contextRefresh = options.invalidateContext === false
220
+ ? undefined
221
+ : invalidateWorktreeContext?.(workspaceId);
222
+ const generation = guard.begin();
223
+ try {
224
+ const view = await viewReader.read(workspaceId);
225
+ if (!guard.isCurrent(generation))
226
+ return;
227
+ if (!workspaceIdsRef.current.includes(workspaceId))
228
+ return;
229
+ setReadState((current) => {
230
+ const remainingTargetError = current.targetError?.workspaceIds.filter((candidate) => candidate !== workspaceId) ?? [];
231
+ const next = {
232
+ ...current,
233
+ status: 'ready',
234
+ views: mergeWorktreeViews(current.views, workspaceIdsRef.current, [view]),
235
+ error: undefined,
236
+ };
237
+ if (remainingTargetError.length === 0) {
238
+ const withoutTargetError = { ...next };
239
+ delete withoutTargetError.targetError;
240
+ return withoutTargetError;
241
+ }
242
+ return {
243
+ ...next,
244
+ targetError: {
245
+ workspaceIds: remainingTargetError,
246
+ error: current.targetError.error,
247
+ },
248
+ };
249
+ });
250
+ }
251
+ catch (error) {
252
+ if (!guard.isCurrent(generation))
253
+ return;
254
+ if (!workspaceIdsRef.current.includes(workspaceId))
255
+ return;
256
+ setReadState((current) => {
257
+ const previousIds = current.targetError?.workspaceIds ?? [];
258
+ return {
259
+ ...current,
260
+ status: 'ready',
261
+ views: mergeWorktreeViews(current.views, workspaceIdsRef.current, []),
262
+ error: undefined,
263
+ targetError: {
264
+ workspaceIds: [...new Set([...previousIds, workspaceId])],
265
+ error: {
266
+ ...toWorktreeViewError(error),
267
+ retryable: true,
268
+ },
269
+ },
270
+ };
271
+ });
272
+ }
273
+ finally {
274
+ await contextRefresh?.catch(() => undefined);
275
+ }
276
+ }));
277
+ }, [invalidateWorktreeContext, manager, viewReader]);
176
278
  const loadModalWorktreeView = useCallback((workspaceId) => {
177
279
  setModalReadError(undefined);
178
280
  setModalReadLoading(true);
@@ -185,7 +287,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
185
287
  });
186
288
  return;
187
289
  }
188
- void modalReadLoader.current.load(manager, workspaceId, (view) => {
290
+ void modalReadLoader.current.load(workspaceId, (view) => {
189
291
  modalReadViewRef.current = view;
190
292
  setModalReadLoading(false);
191
293
  setReadState((current) => ({
@@ -196,13 +298,15 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
196
298
  setModalReadLoading(false);
197
299
  setModalReadError(toWorktreeViewError(error));
198
300
  });
199
- }, [manager]);
301
+ }, [manager, viewReader]);
200
302
  useEffect(() => {
201
303
  if (mode === 'worktree') {
202
304
  void refresh({ preserveCurrent: readStateRef.current.status === 'ready' });
203
305
  }
204
306
  else {
205
307
  refreshGuard.current.invalidate();
308
+ for (const guard of targetRefreshGuards.current.values())
309
+ guard.invalidate();
206
310
  modalReadLoader.current.invalidate();
207
311
  modalReadViewRef.current = undefined;
208
312
  setWorktreeModalWorkspaceId(undefined);
@@ -211,19 +315,67 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
211
315
  setReadState(EMPTY_READ_STATE);
212
316
  }
213
317
  }, [mode, refresh]);
318
+ const previousWorkspaceIdsRef = useRef(workspaceIds);
319
+ useEffect(() => {
320
+ const previousWorkspaceIds = previousWorkspaceIdsRef.current;
321
+ previousWorkspaceIdsRef.current = workspaceIds;
322
+ if (mode !== 'worktree')
323
+ return;
324
+ const addedWorkspaceIds = workspaceIds.filter((workspaceId) => !previousWorkspaceIds.includes(workspaceId));
325
+ const removedWorkspaceIds = previousWorkspaceIds.filter((workspaceId) => !workspaceIds.includes(workspaceId));
326
+ for (const workspaceId of removedWorkspaceIds) {
327
+ targetRefreshGuards.current.delete(workspaceId);
328
+ }
329
+ setReadState((current) => {
330
+ const views = mergeWorktreeViews(current.views, workspaceIds, []);
331
+ const remainingTargetError = current.targetError?.workspaceIds.filter((workspaceId) => workspaceIds.includes(workspaceId)) ?? [];
332
+ const next = { ...current, views };
333
+ if (remainingTargetError.length === 0) {
334
+ const withoutTargetError = { ...next };
335
+ delete withoutTargetError.targetError;
336
+ return withoutTargetError;
337
+ }
338
+ return {
339
+ ...next,
340
+ targetError: {
341
+ workspaceIds: remainingTargetError,
342
+ error: current.targetError.error,
343
+ },
344
+ };
345
+ });
346
+ for (const workspaceId of addedWorkspaceIds) {
347
+ void refresh({
348
+ scope: { kind: 'workspace', workspaceId },
349
+ preserveCurrent: true,
350
+ }).catch(() => {
351
+ // The target error remains visible and can be retried explicitly.
352
+ });
353
+ }
354
+ }, [mode, refresh, workspaceIds]);
214
355
  useEffect(() => {
215
356
  if (forkRecoveryRevisionRef.current === forkRecoverySnapshot.revision)
216
357
  return;
217
358
  forkRecoveryRevisionRef.current = forkRecoverySnapshot.revision;
218
359
  if (mode !== 'worktree' || manager === undefined)
219
360
  return;
361
+ if (forkRecoverySnapshot.affectedWorkspaceIds.length === 0)
362
+ return;
220
363
  void refresh({
364
+ scope: {
365
+ kind: 'workspaces',
366
+ workspaceIds: forkRecoverySnapshot.affectedWorkspaceIds,
367
+ },
221
368
  preserveCurrent: readStateRef.current.status === 'ready',
222
- invalidateContext: false,
223
369
  }).catch(() => {
224
370
  // Recovery state remains visible; the next explicit retry can refresh it again.
225
371
  });
226
- }, [forkRecoverySnapshot.revision, manager, mode, refresh]);
372
+ }, [
373
+ forkRecoverySnapshot.affectedWorkspaceIds,
374
+ forkRecoverySnapshot.revision,
375
+ manager,
376
+ mode,
377
+ refresh,
378
+ ]);
227
379
  useEffect(() => {
228
380
  if (readState.status !== 'ready' || syncSessionWorkspaces === undefined)
229
381
  return;
@@ -376,12 +528,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
376
528
  return createDefaultWorktreeName(existingNames);
377
529
  });
378
530
  }, [modalView, worktreeModalWorkspaceId]);
379
- const runMutation = async (operation) => {
531
+ const runMutation = async (operation, refreshOptions) => {
380
532
  setActionPending(true);
381
533
  setActionError(undefined);
382
534
  try {
383
535
  await operation();
384
- await refresh({ preserveCurrent: true });
536
+ if (refreshOptions !== undefined)
537
+ await refresh(refreshOptions);
385
538
  }
386
539
  catch (error) {
387
540
  setActionError(toWorktreeViewError(error));
@@ -578,7 +731,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
578
731
  }
579
732
  setActionError(undefined);
580
733
  void insertWorktreeBefore(workspaceId, activeDrag.worktreeId, move.beforeWorktreeId)
581
- .then(() => refresh({ preserveCurrent: true, invalidateContext: false }))
734
+ .then(() => refresh({
735
+ scope: { kind: 'workspace', workspaceId },
736
+ preserveCurrent: true,
737
+ invalidateContext: false,
738
+ }))
582
739
  .catch((error) => {
583
740
  setActionError(toRetryableWorktreeOrderError(error));
584
741
  });
@@ -756,7 +913,10 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
756
913
  const continueWorktreeRegistration = async (registeredWorktree) => {
757
914
  closeWorktreeCreator(true);
758
915
  if (createSessionCallback === undefined) {
759
- await refresh({ preserveCurrent: true });
916
+ await refresh({
917
+ scope: { kind: 'workspace', workspaceId: registeredWorktree.workspaceId },
918
+ preserveCurrent: true,
919
+ });
760
920
  setActionError({
761
921
  code: 'WORKTREE_REGISTRATION_SESSION_UNAVAILABLE',
762
922
  message: '',
@@ -771,7 +931,6 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
771
931
  };
772
932
  try {
773
933
  await createSessionCallback(sessionInput);
774
- await invalidateWorktreeContext?.(registeredWorktree.workspaceId);
775
934
  }
776
935
  catch (error) {
777
936
  if (error instanceof WorktreeSessionBindingError && error.retryable) {
@@ -786,7 +945,10 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
786
945
  }
787
946
  throw error;
788
947
  }
789
- await refresh({ preserveCurrent: true });
948
+ await refresh({
949
+ scope: { kind: 'workspace', workspaceId: registeredWorktree.workspaceId },
950
+ preserveCurrent: true,
951
+ });
790
952
  };
791
953
  const submitWorktree = async () => {
792
954
  const worktreeName = newBranch.trim();
@@ -848,8 +1010,10 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
848
1010
  setPendingSessionBinding(undefined);
849
1011
  try {
850
1012
  await createSessionCallback(input);
851
- await invalidateWorktreeContext?.(input.workspaceId);
852
- await refresh({ preserveCurrent: true });
1013
+ await refresh({
1014
+ scope: { kind: 'workspace', workspaceId: input.workspaceId },
1015
+ preserveCurrent: true,
1016
+ });
853
1017
  }
854
1018
  catch (error) {
855
1019
  if (error instanceof WorktreeSessionBindingError && error.retryable) {
@@ -888,8 +1052,10 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
888
1052
  openSession,
889
1053
  });
890
1054
  setPendingSessionBinding(undefined);
891
- await refresh({ preserveCurrent: true });
892
- await invalidateWorktreeContext?.(pending.workspaceId);
1055
+ await refresh({
1056
+ scope: { kind: 'workspace', workspaceId: pending.workspaceId },
1057
+ preserveCurrent: true,
1058
+ });
893
1059
  }
894
1060
  catch (error) {
895
1061
  if (error instanceof WorktreeSessionBindingError && !error.retryable) {
@@ -950,9 +1116,17 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
950
1116
  openSession(sessionId);
951
1117
  }, children: t('action.openCreatedSession') }))] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
952
1118
  setActionError(undefined);
953
- void refresh();
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: () => {
955
- void refresh();
1119
+ void refresh({ scope: { kind: 'global' } });
1120
+ }, children: t('action.retry') }))] })), readState.targetError !== undefined && (_jsxs("div", { className: styles.error, role: "alert", "data-worktree-target-error": true, children: [_jsx("p", { className: styles.message, "data-error": "true", children: formatWorktreeViewError(readState.targetError.error, t) }), _jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
1121
+ void refresh({
1122
+ scope: {
1123
+ kind: 'workspaces',
1124
+ workspaceIds: readState.targetError.workspaceIds,
1125
+ },
1126
+ preserveCurrent: true,
1127
+ });
1128
+ }, 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: () => {
1129
+ void refresh({ scope: { kind: 'global' } });
956
1130
  }, children: t('action.retry') })] })) : readState.status === 'ready' ? (_jsx("div", { className: styles.workspaceList, children: visibleWorkspaces.length === 0 ? (_jsx("p", { className: styles.empty, children: t('workspace.noMatches') })) : (visibleWorkspaces.map((workspace) => {
957
1131
  const view = viewByWorkspace.get(workspace.workspaceId);
958
1132
  const currentBranch = view?.branches.find((branch) => branch.isCurrent)?.name;
@@ -1236,14 +1410,16 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
1236
1410
  mutationToken,
1237
1411
  },
1238
1412
  }, permission, onPermissionNotice);
1239
- await invalidateWorktreeContext?.(target.workspaceId);
1240
1413
  setWorktreeRemoval(undefined);
1414
+ }, {
1415
+ scope: { kind: 'workspace', workspaceId: target.workspaceId },
1416
+ preserveCurrent: true,
1241
1417
  });
1242
1418
  } }), _jsx(RiskConfirmation, { open: fullAccessConfirmationSnapshot !== undefined, title: t('permission.fullAccessTitle'), description: fullAccessConfirmationSnapshot === undefined
1243
1419
  ? ''
1244
1420
  : t('permission.fullAccessDescription', {
1245
1421
  cwd: fullAccessConfirmationSnapshot.cwd,
1246
- }), acknowledgeLabel: t('permission.fullAccessAcknowledge'), cancelLabel: t('dialog.cancel'), confirmLabel: t('permission.fullAccessEnable'), acknowledged: fullAccessAcknowledged, onAcknowledgedChange: setFullAccessAcknowledged, onCancel: () => {
1422
+ }), acknowledgeLabel: t('permission.fullAccessAcknowledge'), cancelLabel: t('dialog.cancel'), closeLabel: t('dialog.close'), confirmLabel: t('permission.fullAccessEnable'), acknowledged: fullAccessAcknowledged, onAcknowledgedChange: setFullAccessAcknowledged, onCancel: () => {
1247
1423
  setFullAccessAcknowledged(false);
1248
1424
  fullAccessConfirmation?.resolve(false);
1249
1425
  }, onConfirm: () => {