@cerbur/clutch-dsh-worktree 0.1.0 → 0.1.1

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.
@@ -4,8 +4,15 @@ import { Button, IconArchiveOutline20, IconBranchOutline16, IconChevronDownOutli
4
4
  import { openWorktreeSession } from './navigation.js';
5
5
  import { useSidebarOverlayGeometry } from './sidebar-overlay-geometry.js';
6
6
  import { effectiveViewMode, unboundSessionIds, workspaceSessionIds } from './view-mode.js';
7
+ import { formatWorktreeViewError } from './worktree-error-copy.js';
7
8
  import { executeWorktreeAction, createDefaultWorktreeName, filterArchivedSessionIds, loadWorktreeViews, selectDefaultBaseBranch, stableWorkspaceIds, toWorktreeViewError, WorktreeSessionBindingError, } from './worktree-view.js';
8
9
  import styles from './worktree.css';
10
+ function toNativeWorktreeViewError(error) {
11
+ const viewError = toWorktreeViewError(error);
12
+ if (typeof error === 'object' && error !== null)
13
+ return viewError;
14
+ return { ...viewError, message: String(error) };
15
+ }
9
16
  const EMPTY_READ_STATE = { status: 'idle', views: [] };
10
17
  function useStableWorkspaceIds(workspaces) {
11
18
  const next = workspaces.map((workspace) => workspace.workspaceId);
@@ -46,17 +53,17 @@ function workspaceMatches(workspace, view, sessions, query) {
46
53
  return view.bindings.some((binding) => includesText(sessionLabel(binding.sessionId, sessions), query));
47
54
  }
48
55
  /** 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, }) {
56
+ function WorktreeWorkspaceRow({ t, workspace, expanded, actionPending, menuOpen, drag, onToggle, onCreateWorktree, onRename, onDelete, onMenuOpenChange, }) {
50
57
  const workspaceMenuItems = [
51
58
  {
52
59
  id: 'rename',
53
- label: 'Rename',
60
+ label: t('workspace.rename'),
54
61
  icon: _jsx(IconEditOutline16, {}),
55
62
  disabled: actionPending,
56
63
  },
57
64
  {
58
65
  id: 'delete',
59
- label: 'Delete',
66
+ label: t('workspace.delete'),
60
67
  icon: _jsx(IconTrashOutline16, {}),
61
68
  danger: true,
62
69
  disabled: actionPending,
@@ -84,7 +91,9 @@ function WorktreeWorkspaceRow({ workspace, expanded, actionPending, menuOpen, dr
84
91
  return;
85
92
  event.preventDefault();
86
93
  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) => {
94
+ }, children: [_jsx("button", { type: "button", className: `${styles.disclosureButton} ${styles.workspaceDisclosure}`, "aria-label": t(expanded ? 'workspace.collapse' : 'workspace.expand', {
95
+ name: workspace.title,
96
+ }), "aria-expanded": expanded, onClick: (event) => {
88
97
  event.stopPropagation();
89
98
  onToggle();
90
99
  }, 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: () => {
@@ -95,27 +104,44 @@ function WorktreeWorkspaceRow({ workspace, expanded, actionPending, menuOpen, dr
95
104
  onRename();
96
105
  if (id === 'delete')
97
106
  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) => {
107
+ }, portal: true, closeOnPointerLeave: true, anchor: (_jsx("button", { type: "button", className: styles.iconButton, "data-workspace-menu": true, "aria-label": t('workspace.options', { name: workspace.title }), onClick: (event) => {
99
108
  event.stopPropagation();
100
109
  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) => {
110
+ }, children: _jsx(IconEllipsisOutline16, {}) })) }) }), _jsx("button", { type: "button", className: styles.iconButton, "data-add-worktree": true, "aria-label": t('workspace.addWorktree', { name: workspace.title }), onClick: (event) => {
102
111
  event.stopPropagation();
103
112
  onCreateWorktree();
104
113
  }, children: _jsx(IconPlusOutline16, {}) })] })] }));
105
114
  }
106
115
  /** Shared Main/Worktree group row with parameterized icon, state, and actions. */
107
- function WorktreeGroupRow({ kind, label, expanded, icon, workspaceTitle, state, stateLabel, onToggle, onCreateSession, }) {
116
+ function WorktreeGroupRow({ t, kind, label, expanded, icon, workspaceTitle, state, stateLabel, onToggle, onCreateSession, menu, }) {
108
117
  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) => {
118
+ return (_jsxs("div", { className: styles.worktreeRow, "data-main-group": main ? 'true' : undefined, "data-main-expanded": main ? String(expanded) : undefined, "data-menu-open": menu?.open ? 'true' : undefined, onClick: onToggle, children: [_jsx("button", { type: "button", className: `${styles.disclosureButton} ${styles.worktreeDisclosure}`, "aria-label": t(expanded ? 'worktree.collapse' : 'worktree.expand', { name: label }), "aria-expanded": expanded, onClick: (event) => {
110
119
  event.stopPropagation();
111
120
  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, {}) })) })] }));
121
+ }, 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 }), _jsxs("span", { className: styles.treeActionSlot, children: [menu !== undefined && (_jsx("span", { className: styles.menuAction, children: _jsx(Menu, { open: menu.open, onClose: () => {
122
+ menu.onOpenChange(false);
123
+ }, items: [
124
+ {
125
+ id: 'remove',
126
+ label: t('worktree.remove'),
127
+ icon: _jsx(IconTrashOutline16, {}),
128
+ danger: true,
129
+ disabled: menu.disabled,
130
+ },
131
+ ], onSelect: (id) => {
132
+ menu.onOpenChange(false);
133
+ if (id === 'remove')
134
+ menu.onRemove();
135
+ }, portal: true, closeOnPointerLeave: true, anchor: _jsx("button", { type: "button", className: styles.iconButton, "data-worktree-menu": true, "aria-label": t('worktree.options', { name: menu.label }), onClick: (event) => {
136
+ event.stopPropagation();
137
+ menu.onOpenChange(!menu.open);
138
+ }, children: _jsx(IconEllipsisOutline16, {}) }) }) })), onCreateSession !== undefined && (_jsx("button", { type: "button", className: styles.iconButton, "data-add-main-session": main ? 'true' : undefined, "data-add-session": main ? undefined : 'true', "aria-label": t('worktree.addSession', { name: workspaceTitle }), onClick: (event) => {
139
+ event.stopPropagation();
140
+ onCreateSession();
141
+ }, children: _jsx(IconPlusOutline16, {}) }))] })] }));
116
142
  }
117
143
  /** 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, }) {
144
+ function WorktreeSessionRow({ t, sessionId, label, drag, actionPending, onOpen, onRename, onFork, onArchive, }) {
119
145
  const [menuOpen, setMenuOpen] = useState(false);
120
146
  const markerClass = drag.marker === 'before'
121
147
  ? styles.dropBefore
@@ -125,19 +151,19 @@ function WorktreeSessionRow({ sessionId, label, drag, actionPending, onOpen, onR
125
151
  const sessionMenuItems = [
126
152
  {
127
153
  id: 'rename',
128
- label: 'Rename',
154
+ label: t('session.rename'),
129
155
  icon: _jsx(IconEditOutline16, {}),
130
156
  disabled: onRename === undefined || actionPending,
131
157
  },
132
158
  {
133
159
  id: 'fork',
134
- label: 'Fork session',
160
+ label: t('session.fork'),
135
161
  icon: _jsx(IconBranchOutline16, {}),
136
162
  disabled: onFork === undefined || actionPending,
137
163
  },
138
164
  {
139
165
  id: 'archive',
140
- label: 'Archive session',
166
+ label: t('session.archive'),
141
167
  icon: _jsx(IconArchiveOutline20, { size: 16 }),
142
168
  disabled: onArchive === undefined || actionPending,
143
169
  },
@@ -167,15 +193,15 @@ function WorktreeSessionRow({ sessionId, label, drag, actionPending, onOpen, onR
167
193
  onFork?.(sessionId);
168
194
  if (id === 'archive')
169
195
  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) => {
196
+ }, portal: true, closeOnPointerLeave: true, anchor: (_jsx("button", { type: "button", className: styles.iconButton, "data-session-menu": true, "aria-label": t('session.options', { name: label }), onClick: (event) => {
171
197
  event.stopPropagation();
172
198
  setMenuOpen((current) => !current);
173
199
  }, children: _jsx(IconEllipsisOutline16, {}) })) }) })] }));
174
200
  }
175
- function WorktreeSessionGroup({ groupKey, sessionIds, workspaceId, expanded, actionPending, sessions, dragState, onToggleExpanded, onStartDrag, onHoverDrag, onClearDrag, onFinishDrag, onCommitDrag, onOpen, onRename, onFork, onArchive, }) {
201
+ function WorktreeSessionGroup({ t, groupKey, sessionIds, workspaceId, expanded, actionPending, sessions, dragState, onToggleExpanded, onStartDrag, onHoverDrag, onClearDrag, onFinishDrag, onCommitDrag, onOpen, onRename, onFork, onArchive, }) {
176
202
  const visibleSessionIds = expanded ? sessionIds : sessionIds.slice(0, 5);
177
203
  const sameGroupDrag = dragState?.groupKey === groupKey;
178
- return (_jsxs(_Fragment, { children: [visibleSessionIds.map((sessionId) => (_jsx(WorktreeSessionRow, { sessionId: sessionId, label: sessionLabel(sessionId, sessions), drag: {
204
+ return (_jsxs(_Fragment, { children: [visibleSessionIds.map((sessionId) => (_jsx(WorktreeSessionRow, { t: t, sessionId: sessionId, label: sessionLabel(sessionId, sessions), drag: {
179
205
  active: sameGroupDrag,
180
206
  marker: sameGroupDrag && dragState.over?.sessionId === sessionId
181
207
  ? dragState.over.half
@@ -202,13 +228,15 @@ function WorktreeSessionGroup({ groupKey, sessionIds, workspaceId, expanded, act
202
228
  },
203
229
  }, actionPending: actionPending, onOpen: () => {
204
230
  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` }))] }));
231
+ }, 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
232
+ ? t('session.collapse')
233
+ : t('session.expandMore', { count: sessionIds.length - 5 }) }))] }));
206
234
  }
207
235
  /**
208
236
  * Peer Worktree navigation surface. It keeps the original DSH Workspace mode
209
237
  * intact and renders a browser-local Workspace → Worktree → Session projection.
210
238
  */
211
- export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, available, manager, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, renameSession, forkSession, archiveSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
239
+ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, t, available, manager, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, renameSession, forkSession, archiveSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
212
240
  const preferredMode = useStore((state) => state.viewMode);
213
241
  const mode = effectiveViewMode(preferredMode, available && manager !== undefined);
214
242
  const sessions = useSessions((state) => state);
@@ -221,6 +249,8 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
221
249
  const [expandedWorktrees, setExpandedWorktrees] = useState({});
222
250
  const [worktreeModalWorkspaceId, setWorktreeModalWorkspaceId] = useState();
223
251
  const [openWorkspaceMenuId, setOpenWorkspaceMenuId] = useState();
252
+ const [openWorktreeMenuId, setOpenWorktreeMenuId] = useState();
253
+ const [worktreeRemoval, setWorktreeRemoval] = useState();
224
254
  const [selectedBranch, setSelectedBranch] = useState('');
225
255
  const [newBranch, setNewBranch] = useState('');
226
256
  const [pendingSessionBinding, setPendingSessionBinding] = useState();
@@ -328,7 +358,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
328
358
  if (workspaceRenameBlocked || target === undefined)
329
359
  return;
330
360
  if (renameWorkspace === undefined) {
331
- setWorkspaceRenameError('Workspace rename is unavailable; retry after reconnecting.');
361
+ setWorkspaceRenameError({
362
+ code: 'WORKSPACE_RENAME_UNAVAILABLE',
363
+ message: '',
364
+ retryable: true,
365
+ });
332
366
  return;
333
367
  }
334
368
  setWorkspaceRenamePending(true);
@@ -338,7 +372,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
338
372
  setWorkspaceRenameTarget(undefined);
339
373
  }
340
374
  catch (error) {
341
- setWorkspaceRenameError(error instanceof Error ? error.message : String(error));
375
+ setWorkspaceRenameError(toNativeWorktreeViewError(error));
342
376
  }
343
377
  finally {
344
378
  setWorkspaceRenamePending(false);
@@ -359,7 +393,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
359
393
  if (target === undefined || workspaceDeletePending)
360
394
  return;
361
395
  if (deleteWorkspace === undefined) {
362
- setWorkspaceDeleteError('Workspace delete is unavailable; retry after reconnecting.');
396
+ setWorkspaceDeleteError({
397
+ code: 'WORKSPACE_DELETE_UNAVAILABLE',
398
+ message: '',
399
+ retryable: true,
400
+ });
363
401
  return;
364
402
  }
365
403
  setWorkspaceDeletePending(true);
@@ -369,7 +407,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
369
407
  setWorkspaceDeleteTarget(undefined);
370
408
  }
371
409
  catch (error) {
372
- setWorkspaceDeleteError(error instanceof Error ? error.message : String(error));
410
+ setWorkspaceDeleteError(toNativeWorktreeViewError(error));
373
411
  }
374
412
  finally {
375
413
  setWorkspaceDeletePending(false);
@@ -399,7 +437,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
399
437
  if (insertWorkspaceBefore === undefined) {
400
438
  setActionError({
401
439
  code: 'WORKSPACE_ORDER_UNAVAILABLE',
402
- message: 'Workspace ordering is unavailable; retry after reconnecting.',
440
+ message: '',
403
441
  retryable: true,
404
442
  });
405
443
  return;
@@ -432,7 +470,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
432
470
  if (insertSessionBefore === undefined) {
433
471
  setActionError({
434
472
  code: 'SESSION_ORDER_UNAVAILABLE',
435
- message: 'Session ordering is unavailable; retry after reconnecting.',
473
+ message: '',
436
474
  retryable: true,
437
475
  });
438
476
  return;
@@ -459,7 +497,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
459
497
  if (target === undefined || title.length === 0 || sessionRenamePending)
460
498
  return;
461
499
  if (renameSession === undefined) {
462
- setSessionRenameError('Session rename is unavailable; retry after reconnecting.');
500
+ setSessionRenameError({
501
+ code: 'SESSION_RENAME_UNAVAILABLE',
502
+ message: '',
503
+ retryable: true,
504
+ });
463
505
  return;
464
506
  }
465
507
  setSessionRenamePending(true);
@@ -469,7 +511,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
469
511
  setSessionRenameTarget(undefined);
470
512
  }
471
513
  catch (error) {
472
- setSessionRenameError(error instanceof Error ? error.message : String(error));
514
+ setSessionRenameError(toNativeWorktreeViewError(error));
473
515
  }
474
516
  finally {
475
517
  setSessionRenamePending(false);
@@ -532,14 +574,18 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
532
574
  input,
533
575
  });
534
576
  if (createdWorktree === undefined) {
535
- throw new Error('Worktree creation returned no Worktree record; retry the request.');
577
+ throw {
578
+ code: 'WORKTREE_RECORD_MISSING',
579
+ message: '',
580
+ retryable: true,
581
+ };
536
582
  }
537
583
  setWorktreeModalWorkspaceId(undefined);
538
584
  if (createSessionCallback === undefined) {
539
585
  await refresh();
540
586
  setActionError({
541
- code: 'SESSION_CREATE_UNAVAILABLE',
542
- message: 'Worktree created, but Session creation is unavailable; retry after reconnecting.',
587
+ code: 'WORKTREE_CREATED_SESSION_UNAVAILABLE',
588
+ message: '',
543
589
  retryable: true,
544
590
  });
545
591
  return;
@@ -571,7 +617,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
571
617
  if (createSessionCallback === undefined) {
572
618
  setActionError({
573
619
  code: 'SESSION_CREATE_UNAVAILABLE',
574
- message: 'Session creation is unavailable; retry after reconnecting.',
620
+ message: '',
575
621
  retryable: true,
576
622
  });
577
623
  return;
@@ -624,31 +670,31 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
624
670
  if (mode !== 'worktree')
625
671
  return null;
626
672
  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: {
673
+ return (_jsxs("aside", { ref: ref, className: styles.surface, "data-worktree-surface": true, "data-collapsed": collapsed || undefined, "aria-label": t('mode.navigation'), style: {
628
674
  width: `${width}px`,
629
675
  ...(bounds.ready
630
676
  ? { top: `${bounds.top}px`, height: `${bounds.height}px` }
631
677
  : { 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: () => {
678
+ }, children: [_jsxs("div", { className: styles.wideContent, children: [_jsxs("header", { className: styles.header, children: [_jsx("span", { className: styles.title, children: t('worktree.title') }), _jsx("button", { type: "button", className: styles.closeButton, "aria-label": t('mode.exit'), onClick: () => {
633
679
  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) => {
680
+ }, 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": t('workspace.search'), placeholder: t('workspace.search'), value: searchQuery, onChange: (event) => {
635
681
  setSearchQuery(event.currentTarget.value);
636
- } }), _jsx("button", { type: "button", className: styles.iconButton, "aria-label": "Add Workspace", onClick: () => {
682
+ } }), _jsx("button", { type: "button", className: styles.iconButton, "aria-label": t('workspace.add'), onClick: () => {
637
683
  if (createWorkspace !== undefined)
638
684
  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: () => {
685
+ }, 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, onClick: () => {
640
686
  void retrySessionBinding();
641
- }, children: "Retry Binding" }), _jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
687
+ }, children: t('action.retryBinding') }), _jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
642
688
  const sessionId = pendingSessionBinding.sessionId;
643
689
  setPendingSessionBinding(undefined);
644
690
  setActionError(undefined);
645
691
  openSession(sessionId);
646
- }, children: "Open Created Session" })] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
692
+ }, children: t('action.openCreatedSession') })] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
647
693
  setActionError(undefined);
648
694
  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: () => {
695
+ }, 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: () => {
650
696
  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) => {
697
+ }, 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) => {
652
698
  const view = viewByWorkspace.get(workspace.workspaceId);
653
699
  const expanded = expandedWorkspaces[workspace.workspaceId] !== false;
654
700
  const workspaceMatchesQuery = includesText(workspace.title, query);
@@ -663,7 +709,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
663
709
  const mainExpanded = expandedMains[workspace.workspaceId] !== false;
664
710
  const mainGroupKey = `main:${workspace.workspaceId}`;
665
711
  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: {
712
+ 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: {
667
713
  active: workspaceDrag !== undefined,
668
714
  marker: workspaceDrag?.over?.workspaceId === workspace.workspaceId
669
715
  ? workspaceDrag.over.half
@@ -707,13 +753,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
707
753
  openWorkspaceDelete(workspace);
708
754
  }, onMenuOpenChange: (open) => {
709
755
  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: () => {
756
+ } }), expanded && (_jsxs("div", { className: styles.treeChildren, children: [_jsx(WorktreeGroupRow, { t: t, kind: "main", label: t('worktree.main'), expanded: mainExpanded, icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, onToggle: () => {
711
757
  toggleMain(workspace.workspaceId);
712
758
  }, onCreateSession: createMainSession === undefined
713
759
  ? undefined
714
760
  : () => {
715
761
  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: () => {
762
+ } }), mainExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: mainGroupKey, sessionIds: visibleMainSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[mainGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
717
763
  setExpandedSessionGroups((current) => ({
718
764
  ...current,
719
765
  [mainGroupKey]: current[mainGroupKey] !== true,
@@ -731,7 +777,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
731
777
  sessionDropCommitted.current = false;
732
778
  }, onCommitDrag: commitSessionDrag, onOpen: (sessionId) => {
733
779
  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) => {
780
+ }, onRename: renameSession === undefined ? undefined : openSessionRename, onFork: forkSession, onArchive: archiveSession === undefined ? undefined : archiveWorktreeSession })), worktrees.length === 0 && (_jsx("p", { className: styles.emptyNested, children: t('worktree.noWorktrees') })), worktrees.map((record) => {
735
781
  const worktreeSessionIds = filterArchivedSessionIds(bindingIdsFor(bindings, record.worktreeId).filter((sessionId) => sessions.ids.includes(sessionId)), archivedSessionIds);
736
782
  const worktreeMatchesQuery = workspaceMatchesQuery ||
737
783
  includesText(record.branch, query) ||
@@ -750,12 +796,12 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
750
796
  ? 'error'
751
797
  : 'done';
752
798
  const stateLabel = record.status === 'removed'
753
- ? 'Detached Worktree'
799
+ ? t('worktree.detached')
754
800
  : record.health === 'repair'
755
- ? 'Worktree needs repair'
756
- : 'Active Worktree ready';
801
+ ? t('worktree.repair')
802
+ : t('worktree.ready');
757
803
  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: () => {
804
+ return (_jsxs("div", { className: styles.worktreeGroup, "data-worktree-id": record.worktreeId, children: [_jsx(WorktreeGroupRow, { t: t, kind: "worktree", label: record.branch, expanded: worktreeExpanded, icon: _jsx(IconBranchOutline16, {}), workspaceTitle: workspace.title, state: state, stateLabel: stateLabel, onToggle: () => {
759
805
  toggleWorktree(record.worktreeId);
760
806
  }, onCreateSession: record.status === 'active'
761
807
  ? () => {
@@ -765,7 +811,20 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
765
811
  cwd: record.absolutePath,
766
812
  });
767
813
  }
768
- : undefined }), worktreeExpanded && (_jsx(WorktreeSessionGroup, { groupKey: worktreeGroupKey, sessionIds: visibleWorktreeSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[worktreeGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
814
+ : undefined, menu: record.status === 'active'
815
+ ? {
816
+ open: openWorktreeMenuId === record.worktreeId,
817
+ label: record.branch,
818
+ disabled: actionPending,
819
+ onOpenChange: (open) => {
820
+ setOpenWorktreeMenuId(open ? record.worktreeId : undefined);
821
+ },
822
+ onRemove: () => {
823
+ setWorktreeRemoval(record);
824
+ setActionError(undefined);
825
+ },
826
+ }
827
+ : undefined }), worktreeExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: worktreeGroupKey, sessionIds: visibleWorktreeSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[worktreeGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
769
828
  setExpandedSessionGroups((current) => ({
770
829
  ...current,
771
830
  [worktreeGroupKey]: current[worktreeGroupKey] !== true,
@@ -787,11 +846,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
787
846
  ? undefined
788
847
  : archiveWorktreeSession }))] }, record.worktreeId));
789
848
  })] }))] }, workspace.workspaceId));
790
- })) })) : null] })] }), _jsx("div", { className: styles.railContent, children: _jsx("button", { type: "button", className: styles.railButton, "aria-label": "Exit Worktree mode", onClick: () => {
849
+ })) })) : null] })] }), _jsx("div", { className: styles.railContent, children: _jsx("button", { type: "button", className: styles.railButton, "aria-label": t('mode.exit'), onClick: () => {
791
850
  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: () => {
851
+ }, children: "WT" }) }), _jsxs(Modal, { open: sessionRenameTarget !== undefined, onClose: closeSessionRename, closeLabel: t('dialog.close'), title: t('session.rename'), footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: sessionRenamePending, onClick: closeSessionRename, children: t('dialog.cancel') }), _jsx(Button, { variant: "primary", disabled: sessionRenamePending || sessionRenameDraft.trim().length === 0, onClick: () => {
793
852
  void confirmSessionRename();
794
- }, children: "Rename" })] })), children: [_jsx(Input, { className: styles.renameInput, value: sessionRenameDraft, "aria-label": "Session name", autoFocus: true, disabled: sessionRenamePending, onFocus: (event) => {
853
+ }, children: t('dialog.rename') })] })), children: [_jsx(Input, { className: styles.renameInput, value: sessionRenameDraft, "aria-label": t('session.name'), autoFocus: true, disabled: sessionRenamePending, onFocus: (event) => {
795
854
  event.currentTarget.select();
796
855
  }, onChange: (event) => {
797
856
  setSessionRenameDraft(event.currentTarget.value);
@@ -801,9 +860,9 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
801
860
  event.preventDefault();
802
861
  void confirmSessionRename();
803
862
  }
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: () => {
863
+ } }), sessionRenameError !== undefined && (_jsx("p", { className: styles.renameError, role: "alert", children: formatWorktreeViewError(sessionRenameError, t) }))] }), workspaceRenameTarget !== undefined && (_jsxs(Modal, { open: workspaceRenameTarget !== undefined, onClose: closeWorkspaceRename, closeLabel: t('dialog.closeWorkspaceRename'), title: t('workspace.renameTitle'), footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: workspaceRenamePending, onClick: closeWorkspaceRename, children: t('dialog.cancel') }), _jsx(Button, { variant: "primary", disabled: workspaceRenameBlocked, onClick: () => {
805
864
  void confirmWorkspaceRename();
806
- }, children: "Rename" })] })), children: [_jsx(Input, { className: styles.renameInput, value: workspaceRenameDraft, "aria-label": "Workspace name", autoFocus: true, disabled: workspaceRenamePending, onFocus: (event) => {
865
+ }, children: t('dialog.rename') })] })), children: [_jsx(Input, { className: styles.renameInput, value: workspaceRenameDraft, "aria-label": t('workspace.name'), autoFocus: true, disabled: workspaceRenamePending, onFocus: (event) => {
807
866
  event.currentTarget.select();
808
867
  }, onChange: (event) => {
809
868
  setWorkspaceRenameDraft(event.currentTarget.value);
@@ -813,22 +872,42 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
813
872
  event.preventDefault();
814
873
  void confirmWorkspaceRename();
815
874
  }
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: () => {
875
+ } }), workspaceRenameDuplicate && (_jsx("p", { className: styles.renameError, role: "alert", children: t('workspace.duplicate') })), workspaceRenameError !== undefined && (_jsx("p", { className: styles.renameError, role: "alert", children: formatWorktreeViewError(workspaceRenameError, t) }))] })), workspaceDeleteTarget !== undefined && (_jsx(Modal, { open: workspaceDeleteTarget !== undefined, onClose: closeWorkspaceDelete, closeLabel: t('dialog.closeWorkspaceDelete'), title: t('workspace.deleteTitle'), description: t('workspace.deleteDescription', { name: workspaceDeleteTarget.title }), footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: workspaceDeletePending, onClick: closeWorkspaceDelete, children: t('dialog.cancel') }), _jsx(Button, { variant: "outline", disabled: workspaceDeletePending, onClick: () => {
817
876
  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: () => {
877
+ }, children: t('dialog.delete') })] })), children: workspaceDeleteError !== undefined && (_jsx("p", { className: styles.renameError, role: "alert", children: formatWorktreeViewError(workspaceDeleteError, t) })) })), worktreeModalWorkspaceId !== undefined && modalWorkspace !== undefined && (_jsxs(Modal, { open: worktreeModalWorkspaceId !== undefined, onClose: () => {
819
878
  if (actionPending)
820
879
  return;
821
880
  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: () => {
881
+ }, closeLabel: t('dialog.closeWorktreeCreate'), title: t('worktree.createTitle'), description: t('worktree.createDescription', { name: modalWorkspace.title }), footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: actionPending, onClick: () => {
823
882
  setWorktreeModalWorkspaceId(undefined);
824
- }, children: "Cancel" }), _jsx(Button, { variant: "primary", disabled: actionPending ||
883
+ }, children: t('dialog.cancel') }), _jsx(Button, { variant: "primary", disabled: actionPending ||
825
884
  selectedBranch.length === 0 ||
826
885
  newBranch.trim().length === 0, onClick: () => {
827
886
  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) => {
887
+ }, children: t('worktree.create') })] })), children: [_jsxs("label", { className: styles.modalField, children: [t('worktree.baseBranch'), _jsxs("select", { className: styles.actionSelect, "aria-label": t('worktree.baseBranch'), value: selectedBranch, disabled: actionPending, onChange: (event) => {
829
888
  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) => {
889
+ }, children: [_jsx("option", { value: "", children: t('worktree.noLocalBranch') }), (modalView?.branches ?? []).map((branch) => (_jsxs("option", { value: branch.name, children: [branch.name, branch.isCurrent ? t('branch.current') : '', branch.checkedOut ? t('branch.checkedOut') : ''] }, branch.name)))] })] }), _jsxs("label", { className: styles.modalField, children: [t('worktree.name'), _jsx(Input, { className: styles.renameInput, "aria-label": t('worktree.name'), value: newBranch, placeholder: "dsh/12345678", disabled: actionPending, onChange: (event) => {
831
890
  setNewBranch(event.currentTarget.value);
832
- } })] }), modalView?.branches.length === 0 && (_jsx("p", { className: styles.empty, children: "No local branches found in this Workspace." }))] }))] }));
891
+ } })] }), modalView?.branches.length === 0 && (_jsx("p", { className: styles.empty, children: t('worktree.noBranches') }))] })), worktreeRemoval !== undefined && (_jsx(Modal, { open: worktreeRemoval !== undefined, onClose: () => {
892
+ if (actionPending)
893
+ return;
894
+ setWorktreeRemoval(undefined);
895
+ }, closeLabel: t('dialog.closeWorktreeRemove'), title: t('worktree.remove'), description: t('worktree.removeDescription', { name: worktreeRemoval.branch }), footer: (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: actionPending, onClick: () => {
896
+ setWorktreeRemoval(undefined);
897
+ }, children: t('dialog.cancel') }), _jsx(Button, { variant: "primary", disabled: actionPending, onClick: () => {
898
+ if (manager === undefined)
899
+ return;
900
+ const target = worktreeRemoval;
901
+ void runMutation(async () => {
902
+ await executeWorktreeAction(manager, {
903
+ type: 'removeWorktree',
904
+ input: {
905
+ workspaceId: target.workspaceId,
906
+ worktreeId: target.worktreeId,
907
+ },
908
+ });
909
+ setWorktreeRemoval(undefined);
910
+ });
911
+ }, children: t('worktree.remove') })] })) }))] }));
833
912
  }
834
913
  //# sourceMappingURL=WorktreeSurface.js.map