@cerbur/clutch-dsh-worktree 0.1.0 → 0.1.2

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.
@@ -1,11 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { Button, IconArchiveOutline20, IconBranchOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFolderClose16, IconFolderOpen16, IconPlusOutline16, IconSearchOutline16, IconTrashOutline16, Input, Menu, Modal, StateDot, } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { Button, HoverCard, IconArchiveOutline20, IconBranchOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFolderClose16, IconFolderOpen16, IconPlusOutline16, IconSearchOutline16, IconTrashOutline16, Input, Menu, Modal, StateDot, } 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 { 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,47 @@ 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
+ const row = (_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, {}) }))] })] }));
142
+ if (main)
143
+ return row;
144
+ return (_jsx(HoverCard, { anchor: row, content: _jsx("div", { className: styles.worktreeHoverTitle, children: label }), openDelayMs: 500, disabled: menu?.open === true }));
116
145
  }
117
146
  /** 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, }) {
147
+ function WorktreeSessionRow({ t, sessionId, label, drag, actionPending, onOpen, onRename, onFork, onArchive, }) {
119
148
  const [menuOpen, setMenuOpen] = useState(false);
120
149
  const markerClass = drag.marker === 'before'
121
150
  ? styles.dropBefore
@@ -125,19 +154,19 @@ function WorktreeSessionRow({ sessionId, label, drag, actionPending, onOpen, onR
125
154
  const sessionMenuItems = [
126
155
  {
127
156
  id: 'rename',
128
- label: 'Rename',
157
+ label: t('session.rename'),
129
158
  icon: _jsx(IconEditOutline16, {}),
130
159
  disabled: onRename === undefined || actionPending,
131
160
  },
132
161
  {
133
162
  id: 'fork',
134
- label: 'Fork session',
163
+ label: t('session.fork'),
135
164
  icon: _jsx(IconBranchOutline16, {}),
136
165
  disabled: onFork === undefined || actionPending,
137
166
  },
138
167
  {
139
168
  id: 'archive',
140
- label: 'Archive session',
169
+ label: t('session.archive'),
141
170
  icon: _jsx(IconArchiveOutline20, { size: 16 }),
142
171
  disabled: onArchive === undefined || actionPending,
143
172
  },
@@ -167,15 +196,15 @@ function WorktreeSessionRow({ sessionId, label, drag, actionPending, onOpen, onR
167
196
  onFork?.(sessionId);
168
197
  if (id === 'archive')
169
198
  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) => {
199
+ }, 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
200
  event.stopPropagation();
172
201
  setMenuOpen((current) => !current);
173
202
  }, children: _jsx(IconEllipsisOutline16, {}) })) }) })] }));
174
203
  }
175
- function WorktreeSessionGroup({ groupKey, sessionIds, workspaceId, expanded, actionPending, sessions, dragState, onToggleExpanded, onStartDrag, onHoverDrag, onClearDrag, onFinishDrag, onCommitDrag, onOpen, onRename, onFork, onArchive, }) {
204
+ function WorktreeSessionGroup({ t, groupKey, sessionIds, workspaceId, expanded, actionPending, sessions, dragState, onToggleExpanded, onStartDrag, onHoverDrag, onClearDrag, onFinishDrag, onCommitDrag, onOpen, onRename, onFork, onArchive, }) {
176
205
  const visibleSessionIds = expanded ? sessionIds : sessionIds.slice(0, 5);
177
206
  const sameGroupDrag = dragState?.groupKey === groupKey;
178
- return (_jsxs(_Fragment, { children: [visibleSessionIds.map((sessionId) => (_jsx(WorktreeSessionRow, { sessionId: sessionId, label: sessionLabel(sessionId, sessions), drag: {
207
+ return (_jsxs(_Fragment, { children: [visibleSessionIds.map((sessionId) => (_jsx(WorktreeSessionRow, { t: t, sessionId: sessionId, label: sessionLabel(sessionId, sessions), drag: {
179
208
  active: sameGroupDrag,
180
209
  marker: sameGroupDrag && dragState.over?.sessionId === sessionId
181
210
  ? dragState.over.half
@@ -202,13 +231,15 @@ function WorktreeSessionGroup({ groupKey, sessionIds, workspaceId, expanded, act
202
231
  },
203
232
  }, actionPending: actionPending, onOpen: () => {
204
233
  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` }))] }));
234
+ }, 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
235
+ ? t('session.collapse')
236
+ : t('session.expandMore', { count: sessionIds.length - 5 }) }))] }));
206
237
  }
207
238
  /**
208
239
  * Peer Worktree navigation surface. It keeps the original DSH Workspace mode
209
240
  * intact and renders a browser-local Workspace → Worktree → Session projection.
210
241
  */
211
- export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces, available, manager, createWorkspace, createSessionForWorktree: createSessionCallback, createMainSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, insertSessionBefore, renameSession, forkSession, archiveSession, ensureSessionWorkspace, syncSessionWorkspaces, openSession, }) {
242
+ 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
243
  const preferredMode = useStore((state) => state.viewMode);
213
244
  const mode = effectiveViewMode(preferredMode, available && manager !== undefined);
214
245
  const sessions = useSessions((state) => state);
@@ -221,6 +252,8 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
221
252
  const [expandedWorktrees, setExpandedWorktrees] = useState({});
222
253
  const [worktreeModalWorkspaceId, setWorktreeModalWorkspaceId] = useState();
223
254
  const [openWorkspaceMenuId, setOpenWorkspaceMenuId] = useState();
255
+ const [openWorktreeMenuId, setOpenWorktreeMenuId] = useState();
256
+ const [worktreeRemoval, setWorktreeRemoval] = useState();
224
257
  const [selectedBranch, setSelectedBranch] = useState('');
225
258
  const [newBranch, setNewBranch] = useState('');
226
259
  const [pendingSessionBinding, setPendingSessionBinding] = useState();
@@ -328,7 +361,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
328
361
  if (workspaceRenameBlocked || target === undefined)
329
362
  return;
330
363
  if (renameWorkspace === undefined) {
331
- setWorkspaceRenameError('Workspace rename is unavailable; retry after reconnecting.');
364
+ setWorkspaceRenameError({
365
+ code: 'WORKSPACE_RENAME_UNAVAILABLE',
366
+ message: '',
367
+ retryable: true,
368
+ });
332
369
  return;
333
370
  }
334
371
  setWorkspaceRenamePending(true);
@@ -338,7 +375,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
338
375
  setWorkspaceRenameTarget(undefined);
339
376
  }
340
377
  catch (error) {
341
- setWorkspaceRenameError(error instanceof Error ? error.message : String(error));
378
+ setWorkspaceRenameError(toNativeWorktreeViewError(error));
342
379
  }
343
380
  finally {
344
381
  setWorkspaceRenamePending(false);
@@ -359,7 +396,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
359
396
  if (target === undefined || workspaceDeletePending)
360
397
  return;
361
398
  if (deleteWorkspace === undefined) {
362
- setWorkspaceDeleteError('Workspace delete is unavailable; retry after reconnecting.');
399
+ setWorkspaceDeleteError({
400
+ code: 'WORKSPACE_DELETE_UNAVAILABLE',
401
+ message: '',
402
+ retryable: true,
403
+ });
363
404
  return;
364
405
  }
365
406
  setWorkspaceDeletePending(true);
@@ -369,7 +410,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
369
410
  setWorkspaceDeleteTarget(undefined);
370
411
  }
371
412
  catch (error) {
372
- setWorkspaceDeleteError(error instanceof Error ? error.message : String(error));
413
+ setWorkspaceDeleteError(toNativeWorktreeViewError(error));
373
414
  }
374
415
  finally {
375
416
  setWorkspaceDeletePending(false);
@@ -399,7 +440,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
399
440
  if (insertWorkspaceBefore === undefined) {
400
441
  setActionError({
401
442
  code: 'WORKSPACE_ORDER_UNAVAILABLE',
402
- message: 'Workspace ordering is unavailable; retry after reconnecting.',
443
+ message: '',
403
444
  retryable: true,
404
445
  });
405
446
  return;
@@ -432,7 +473,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
432
473
  if (insertSessionBefore === undefined) {
433
474
  setActionError({
434
475
  code: 'SESSION_ORDER_UNAVAILABLE',
435
- message: 'Session ordering is unavailable; retry after reconnecting.',
476
+ message: '',
436
477
  retryable: true,
437
478
  });
438
479
  return;
@@ -459,7 +500,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
459
500
  if (target === undefined || title.length === 0 || sessionRenamePending)
460
501
  return;
461
502
  if (renameSession === undefined) {
462
- setSessionRenameError('Session rename is unavailable; retry after reconnecting.');
503
+ setSessionRenameError({
504
+ code: 'SESSION_RENAME_UNAVAILABLE',
505
+ message: '',
506
+ retryable: true,
507
+ });
463
508
  return;
464
509
  }
465
510
  setSessionRenamePending(true);
@@ -469,7 +514,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
469
514
  setSessionRenameTarget(undefined);
470
515
  }
471
516
  catch (error) {
472
- setSessionRenameError(error instanceof Error ? error.message : String(error));
517
+ setSessionRenameError(toNativeWorktreeViewError(error));
473
518
  }
474
519
  finally {
475
520
  setSessionRenamePending(false);
@@ -532,14 +577,18 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
532
577
  input,
533
578
  });
534
579
  if (createdWorktree === undefined) {
535
- throw new Error('Worktree creation returned no Worktree record; retry the request.');
580
+ throw {
581
+ code: 'WORKTREE_RECORD_MISSING',
582
+ message: '',
583
+ retryable: true,
584
+ };
536
585
  }
537
586
  setWorktreeModalWorkspaceId(undefined);
538
587
  if (createSessionCallback === undefined) {
539
588
  await refresh();
540
589
  setActionError({
541
- code: 'SESSION_CREATE_UNAVAILABLE',
542
- message: 'Worktree created, but Session creation is unavailable; retry after reconnecting.',
590
+ code: 'WORKTREE_CREATED_SESSION_UNAVAILABLE',
591
+ message: '',
543
592
  retryable: true,
544
593
  });
545
594
  return;
@@ -571,7 +620,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
571
620
  if (createSessionCallback === undefined) {
572
621
  setActionError({
573
622
  code: 'SESSION_CREATE_UNAVAILABLE',
574
- message: 'Session creation is unavailable; retry after reconnecting.',
623
+ message: '',
575
624
  retryable: true,
576
625
  });
577
626
  return;
@@ -624,31 +673,31 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
624
673
  if (mode !== 'worktree')
625
674
  return null;
626
675
  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: {
676
+ return (_jsxs("aside", { ref: ref, className: styles.surface, "data-worktree-surface": true, "data-collapsed": collapsed || undefined, "aria-label": t('mode.navigation'), style: {
628
677
  width: `${width}px`,
629
678
  ...(bounds.ready
630
679
  ? { top: `${bounds.top}px`, height: `${bounds.height}px` }
631
680
  : { 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: () => {
681
+ }, 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
682
  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) => {
683
+ }, 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
684
  setSearchQuery(event.currentTarget.value);
636
- } }), _jsx("button", { type: "button", className: styles.iconButton, "aria-label": "Add Workspace", onClick: () => {
685
+ } }), _jsx("button", { type: "button", className: styles.iconButton, "aria-label": t('workspace.add'), onClick: () => {
637
686
  if (createWorkspace !== undefined)
638
687
  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: () => {
688
+ }, 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
689
  void retrySessionBinding();
641
- }, children: "Retry Binding" }), _jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
690
+ }, children: t('action.retryBinding') }), _jsx("button", { type: "button", className: styles.actionButton, disabled: actionPending, onClick: () => {
642
691
  const sessionId = pendingSessionBinding.sessionId;
643
692
  setPendingSessionBinding(undefined);
644
693
  setActionError(undefined);
645
694
  openSession(sessionId);
646
- }, children: "Open Created Session" })] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
695
+ }, children: t('action.openCreatedSession') })] })), actionError.retryable && (_jsx("button", { type: "button", className: styles.retryButton, onClick: () => {
647
696
  setActionError(undefined);
648
697
  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: () => {
698
+ }, 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
699
  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) => {
700
+ }, 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
701
  const view = viewByWorkspace.get(workspace.workspaceId);
653
702
  const expanded = expandedWorkspaces[workspace.workspaceId] !== false;
654
703
  const workspaceMatchesQuery = includesText(workspace.title, query);
@@ -663,7 +712,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
663
712
  const mainExpanded = expandedMains[workspace.workspaceId] !== false;
664
713
  const mainGroupKey = `main:${workspace.workspaceId}`;
665
714
  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: {
715
+ 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
716
  active: workspaceDrag !== undefined,
668
717
  marker: workspaceDrag?.over?.workspaceId === workspace.workspaceId
669
718
  ? workspaceDrag.over.half
@@ -707,13 +756,13 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
707
756
  openWorkspaceDelete(workspace);
708
757
  }, onMenuOpenChange: (open) => {
709
758
  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: () => {
759
+ } }), 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
760
  toggleMain(workspace.workspaceId);
712
761
  }, onCreateSession: createMainSession === undefined
713
762
  ? undefined
714
763
  : () => {
715
764
  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: () => {
765
+ } }), mainExpanded && (_jsx(WorktreeSessionGroup, { t: t, groupKey: mainGroupKey, sessionIds: visibleMainSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[mainGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
717
766
  setExpandedSessionGroups((current) => ({
718
767
  ...current,
719
768
  [mainGroupKey]: current[mainGroupKey] !== true,
@@ -731,7 +780,7 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
731
780
  sessionDropCommitted.current = false;
732
781
  }, onCommitDrag: commitSessionDrag, onOpen: (sessionId) => {
733
782
  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) => {
783
+ }, 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
784
  const worktreeSessionIds = filterArchivedSessionIds(bindingIdsFor(bindings, record.worktreeId).filter((sessionId) => sessions.ids.includes(sessionId)), archivedSessionIds);
736
785
  const worktreeMatchesQuery = workspaceMatchesQuery ||
737
786
  includesText(record.branch, query) ||
@@ -750,12 +799,12 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
750
799
  ? 'error'
751
800
  : 'done';
752
801
  const stateLabel = record.status === 'removed'
753
- ? 'Detached Worktree'
802
+ ? t('worktree.detached')
754
803
  : record.health === 'repair'
755
- ? 'Worktree needs repair'
756
- : 'Active Worktree ready';
804
+ ? t('worktree.repair')
805
+ : t('worktree.ready');
757
806
  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: () => {
807
+ 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
808
  toggleWorktree(record.worktreeId);
760
809
  }, onCreateSession: record.status === 'active'
761
810
  ? () => {
@@ -765,7 +814,20 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
765
814
  cwd: record.absolutePath,
766
815
  });
767
816
  }
768
- : undefined }), worktreeExpanded && (_jsx(WorktreeSessionGroup, { groupKey: worktreeGroupKey, sessionIds: visibleWorktreeSessionIds, workspaceId: workspace.workspaceId, expanded: expandedSessionGroups[worktreeGroupKey] === true, actionPending: actionPending, sessions: sessions, dragState: sessionDrag, onToggleExpanded: () => {
817
+ : undefined, menu: record.status === 'active'
818
+ ? {
819
+ open: openWorktreeMenuId === record.worktreeId,
820
+ label: record.branch,
821
+ disabled: actionPending,
822
+ onOpenChange: (open) => {
823
+ setOpenWorktreeMenuId(open ? record.worktreeId : undefined);
824
+ },
825
+ onRemove: () => {
826
+ setWorktreeRemoval(record);
827
+ setActionError(undefined);
828
+ },
829
+ }
830
+ : 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
831
  setExpandedSessionGroups((current) => ({
770
832
  ...current,
771
833
  [worktreeGroupKey]: current[worktreeGroupKey] !== true,
@@ -787,11 +849,11 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
787
849
  ? undefined
788
850
  : archiveWorktreeSession }))] }, record.worktreeId));
789
851
  })] }))] }, workspace.workspaceId));
790
- })) })) : null] })] }), _jsx("div", { className: styles.railContent, children: _jsx("button", { type: "button", className: styles.railButton, "aria-label": "Exit Worktree mode", onClick: () => {
852
+ })) })) : null] })] }), _jsx("div", { className: styles.railContent, children: _jsx("button", { type: "button", className: styles.railButton, "aria-label": t('mode.exit'), onClick: () => {
791
853
  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: () => {
854
+ }, 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
855
  void confirmSessionRename();
794
- }, children: "Rename" })] })), children: [_jsx(Input, { className: styles.renameInput, value: sessionRenameDraft, "aria-label": "Session name", autoFocus: true, disabled: sessionRenamePending, onFocus: (event) => {
856
+ }, children: t('dialog.rename') })] })), children: [_jsx(Input, { className: styles.renameInput, value: sessionRenameDraft, "aria-label": t('session.name'), autoFocus: true, disabled: sessionRenamePending, onFocus: (event) => {
795
857
  event.currentTarget.select();
796
858
  }, onChange: (event) => {
797
859
  setSessionRenameDraft(event.currentTarget.value);
@@ -801,9 +863,9 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
801
863
  event.preventDefault();
802
864
  void confirmSessionRename();
803
865
  }
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: () => {
866
+ } }), 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
867
  void confirmWorkspaceRename();
806
- }, children: "Rename" })] })), children: [_jsx(Input, { className: styles.renameInput, value: workspaceRenameDraft, "aria-label": "Workspace name", autoFocus: true, disabled: workspaceRenamePending, onFocus: (event) => {
868
+ }, children: t('dialog.rename') })] })), children: [_jsx(Input, { className: styles.renameInput, value: workspaceRenameDraft, "aria-label": t('workspace.name'), autoFocus: true, disabled: workspaceRenamePending, onFocus: (event) => {
807
869
  event.currentTarget.select();
808
870
  }, onChange: (event) => {
809
871
  setWorkspaceRenameDraft(event.currentTarget.value);
@@ -813,22 +875,42 @@ export function WorktreeSurface({ useStore, actions, useSessions, useWorkspaces,
813
875
  event.preventDefault();
814
876
  void confirmWorkspaceRename();
815
877
  }
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: () => {
878
+ } }), 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
879
  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: () => {
880
+ }, 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
881
  if (actionPending)
820
882
  return;
821
883
  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: () => {
884
+ }, 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
885
  setWorktreeModalWorkspaceId(undefined);
824
- }, children: "Cancel" }), _jsx(Button, { variant: "primary", disabled: actionPending ||
886
+ }, children: t('dialog.cancel') }), _jsx(Button, { variant: "primary", disabled: actionPending ||
825
887
  selectedBranch.length === 0 ||
826
888
  newBranch.trim().length === 0, onClick: () => {
827
889
  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) => {
890
+ }, 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
891
  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) => {
892
+ }, 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
893
  setNewBranch(event.currentTarget.value);
832
- } })] }), modalView?.branches.length === 0 && (_jsx("p", { className: styles.empty, children: "No local branches found in this Workspace." }))] }))] }));
894
+ } })] }), modalView?.branches.length === 0 && (_jsx("p", { className: styles.empty, children: t('worktree.noBranches') }))] })), worktreeRemoval !== undefined && (_jsx(Modal, { open: worktreeRemoval !== undefined, onClose: () => {
895
+ if (actionPending)
896
+ return;
897
+ setWorktreeRemoval(undefined);
898
+ }, 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: () => {
899
+ setWorktreeRemoval(undefined);
900
+ }, children: t('dialog.cancel') }), _jsx(Button, { variant: "primary", disabled: actionPending, onClick: () => {
901
+ if (manager === undefined)
902
+ return;
903
+ const target = worktreeRemoval;
904
+ void runMutation(async () => {
905
+ await executeWorktreeAction(manager, {
906
+ type: 'removeWorktree',
907
+ input: {
908
+ workspaceId: target.workspaceId,
909
+ worktreeId: target.worktreeId,
910
+ },
911
+ });
912
+ setWorktreeRemoval(undefined);
913
+ });
914
+ }, children: t('worktree.remove') })] })) }))] }));
833
915
  }
834
916
  //# sourceMappingURL=WorktreeSurface.js.map