claude-ws 0.3.97 → 0.3.99

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 (86) hide show
  1. package/locales/de.json +374 -12
  2. package/locales/en.json +374 -12
  3. package/locales/es.json +398 -11
  4. package/locales/fr.json +398 -11
  5. package/locales/ja.json +398 -11
  6. package/locales/ko.json +398 -11
  7. package/locales/vi.json +374 -12
  8. package/locales/zh.json +398 -11
  9. package/package.json +1 -1
  10. package/server.ts +283 -6
  11. package/src/app/[locale]/not-found.tsx +6 -3
  12. package/src/app/[locale]/page.tsx +14 -4
  13. package/src/app/api/attempts/[id]/workflow/route.ts +76 -0
  14. package/src/app/api/questions/answer/route.ts +58 -0
  15. package/src/app/api/questions/route.ts +68 -0
  16. package/src/app/api/tasks/[id]/compact/route.ts +62 -0
  17. package/src/components/access-anywhere/api-access-key-setup-modal.tsx +2 -2
  18. package/src/components/access-anywhere/tunnel-settings-dialog.tsx +6 -6
  19. package/src/components/access-anywhere/wizard-step-ctunnel.tsx +8 -8
  20. package/src/components/agent-factory/dependency-tree.tsx +5 -3
  21. package/src/components/agent-factory/discovery-dialog.tsx +26 -22
  22. package/src/components/agent-factory/plugin-detail-dialog.tsx +41 -38
  23. package/src/components/agent-factory/plugin-form-dialog.tsx +23 -20
  24. package/src/components/agent-factory/plugin-list.tsx +20 -17
  25. package/src/components/agent-factory/upload-dialog.tsx +17 -14
  26. package/src/components/auth/agent-provider-dialog.tsx +67 -65
  27. package/src/components/auth/api-key-dialog.tsx +14 -11
  28. package/src/components/auth/auth-error-message.tsx +6 -3
  29. package/src/components/editor/code-editor-with-inline-edit.tsx +4 -2
  30. package/src/components/editor/file-diff-resolver-modal.tsx +31 -26
  31. package/src/components/editor/inline-edit-dialog.tsx +9 -6
  32. package/src/components/editor/selection-mention-popup.tsx +3 -1
  33. package/src/components/header/project-selector.tsx +7 -4
  34. package/src/components/header.tsx +70 -4
  35. package/src/components/kanban/column.tsx +11 -0
  36. package/src/components/kanban/task-card.tsx +70 -4
  37. package/src/components/project-settings/component-selector.tsx +3 -1
  38. package/src/components/project-settings/plugin-upload-dialog.tsx +7 -5
  39. package/src/components/project-settings/project-settings-dialog.tsx +5 -3
  40. package/src/components/questions/questions-panel.tsx +136 -0
  41. package/src/components/settings/folder-browser-dialog.tsx +29 -25
  42. package/src/components/settings/settings-page.tsx +64 -18
  43. package/src/components/settings/setup-dialog.tsx +26 -23
  44. package/src/components/setup/unified-setup-wizard.tsx +12 -9
  45. package/src/components/sidebar/file-browser/file-create-buttons.tsx +7 -3
  46. package/src/components/sidebar/file-browser/file-tab-content.tsx +19 -15
  47. package/src/components/sidebar/file-browser/file-tabs-panel.tsx +7 -4
  48. package/src/components/sidebar/file-browser/file-tree.tsx +3 -1
  49. package/src/components/sidebar/git-changes/branch-checkout-modal.tsx +6 -4
  50. package/src/components/sidebar/git-changes/commit-details-modal.tsx +5 -3
  51. package/src/components/sidebar/git-changes/diff-tabs-panel.tsx +3 -1
  52. package/src/components/sidebar/git-changes/git-file-item.tsx +8 -6
  53. package/src/components/sidebar/git-changes/git-graph.tsx +8 -5
  54. package/src/components/sidebar/git-changes/git-panel.tsx +28 -27
  55. package/src/components/sidebar/git-changes/git-section.tsx +5 -3
  56. package/src/components/sidebar/shells/shell-panel.tsx +3 -1
  57. package/src/components/task/attachment-bar.tsx +4 -1
  58. package/src/components/task/attempt-item.tsx +7 -5
  59. package/src/components/task/conversation-view.tsx +21 -13
  60. package/src/components/task/floating-chat-window.tsx +14 -5
  61. package/src/components/task/interactive-command/checkpoint-list.tsx +5 -3
  62. package/src/components/task/interactive-command/confirm-dialog.tsx +9 -4
  63. package/src/components/task/interactive-command/interactive-command-overlay.tsx +23 -9
  64. package/src/components/task/interactive-command/question-prompt.tsx +12 -8
  65. package/src/components/task/pending-question-indicator.tsx +5 -3
  66. package/src/components/task/prompt-input.tsx +1 -1
  67. package/src/components/task/shell-log-view.tsx +3 -1
  68. package/src/components/task/status-line.tsx +84 -23
  69. package/src/components/task/task-detail-panel.tsx +27 -27
  70. package/src/components/task/task-shell-indicator.tsx +10 -6
  71. package/src/components/terminal/terminal-context-menu.tsx +6 -4
  72. package/src/components/terminal/terminal-instance.tsx +11 -3
  73. package/src/components/terminal/terminal-panel.tsx +6 -3
  74. package/src/components/terminal/terminal-shortcut-bar.tsx +3 -1
  75. package/src/components/terminal/terminal-tab-bar.tsx +5 -3
  76. package/src/components/workflow/workflow-panel.tsx +181 -0
  77. package/src/hooks/use-attempt-stream.ts +96 -3
  78. package/src/lib/agent-manager.ts +89 -3
  79. package/src/lib/db/index.ts +18 -0
  80. package/src/lib/db/schema.ts +29 -0
  81. package/src/lib/process-manager.ts +28 -7
  82. package/src/lib/session-manager.ts +60 -0
  83. package/src/lib/usage-tracker.ts +19 -19
  84. package/src/lib/workflow-tracker.ts +118 -20
  85. package/src/stores/questions-store.ts +76 -0
  86. package/src/stores/workflow-store.ts +71 -0
@@ -9,6 +9,7 @@ import { useResizable } from '@/hooks/use-resizable';
9
9
  import { useSidebarStore } from '@/stores/sidebar-store';
10
10
  import { usePanelLayoutStore, PANEL_CONFIGS } from '@/stores/panel-layout-store';
11
11
  import { cn } from '@/lib/utils';
12
+ import { useTranslations } from 'next-intl';
12
13
 
13
14
  const { minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH } = PANEL_CONFIGS.filePreview;
14
15
 
@@ -35,6 +36,8 @@ function trimFileName(fileName: string, maxLength = 25): string {
35
36
  }
36
37
 
37
38
  export function FileTabsPanel() {
39
+ const tCommon = useTranslations('common');
40
+ const tEditor = useTranslations('editor');
38
41
  const {
39
42
  openTabs,
40
43
  activeTabId,
@@ -59,7 +62,7 @@ export function FileTabsPanel() {
59
62
  const tab = openTabs.find(t => t.id === tabId);
60
63
  if (tab?.isDirty) {
61
64
  const fileName = tab.filePath.split('/').pop() || tab.filePath;
62
- if (!confirm(`"${fileName}" has unsaved changes. Close anyway?`)) {
65
+ if (!confirm(tCommon('unsavedChangesConfirm', { fileName }))) {
63
66
  return;
64
67
  }
65
68
  }
@@ -71,7 +74,7 @@ export function FileTabsPanel() {
71
74
  const dirtyTabs = openTabs.filter(t => t.isDirty);
72
75
  if (dirtyTabs.length > 0) {
73
76
  const fileNames = dirtyTabs.map(t => t.filePath.split('/').pop()).join(', ');
74
- if (!confirm(`You have unsaved changes in: ${fileNames}\n\nClose all tabs anyway?`)) {
77
+ if (!confirm(tCommon('unsavedChangesConfirm', { fileName: fileNames }))) {
75
78
  return;
76
79
  }
77
80
  }
@@ -159,9 +162,9 @@ export function FileTabsPanel() {
159
162
  size="sm"
160
163
  onClick={handleCloseAllTabs}
161
164
  className="text-xs text-muted-foreground h-8 px-2 mr-1"
162
- title="Close all tabs"
165
+ title={tEditor('closeAllTabs')}
163
166
  >
164
- Close all
167
+ {tEditor('closeAllTabs')}
165
168
  </Button>
166
169
  )}
167
170
  </div>
@@ -10,6 +10,7 @@ import { useSidebarStore } from '@/stores/sidebar-store';
10
10
  import { useActiveProject } from '@/hooks/use-active-project';
11
11
  import type { FileEntry } from '@/types';
12
12
  import { FileCreateButtons } from './file-create-buttons';
13
+ import { useTranslations } from 'next-intl';
13
14
 
14
15
  interface FileTreeProps {
15
16
  onFileSelect?: (path: string, lineNumber?: number, column?: number, matchLength?: number) => void;
@@ -17,6 +18,7 @@ interface FileTreeProps {
17
18
 
18
19
  export function FileTree({ onFileSelect }: FileTreeProps) {
19
20
  const activeProject = useActiveProject();
21
+ const tSidebar = useTranslations('sidebar');
20
22
  const { expandedFolders, toggleFolder, selectedFile, setSelectedFile, openTab, setEditorPosition } =
21
23
  useSidebarStore();
22
24
 
@@ -183,7 +185,7 @@ export function FileTree({ onFileSelect }: FileTreeProps) {
183
185
  if (!activeProject) {
184
186
  return (
185
187
  <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
186
- No project selected
188
+ {tSidebar('selectProject')}
187
189
  </div>
188
190
  );
189
191
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
4
  import { Loader2, GitBranch, Check } from 'lucide-react';
5
+ import { useTranslations } from 'next-intl';
5
6
  import {
6
7
  Dialog,
7
8
  DialogContent,
@@ -41,6 +42,7 @@ export function BranchCheckoutModal({
41
42
  currentBranch,
42
43
  onCheckout,
43
44
  }: BranchCheckoutModalProps) {
45
+ const t = useTranslations('git');
44
46
  const [search, setSearch] = useState('');
45
47
  const [branches, setBranches] = useState<{ local: Branch[]; remote: Branch[] }>({
46
48
  local: [],
@@ -59,7 +61,7 @@ export function BranchCheckoutModal({
59
61
  const res = await fetch(`/api/git/branches?path=${encodeURIComponent(projectPath)}`);
60
62
  if (!res.ok) {
61
63
  const data = await res.json();
62
- throw new Error(data.error || 'Failed to fetch branches');
64
+ throw new Error(data.error || t('failedToFetchBranches'));
63
65
  }
64
66
  const data = await res.json();
65
67
  setBranches({
@@ -67,7 +69,7 @@ export function BranchCheckoutModal({
67
69
  remote: data.remoteBranches || [],
68
70
  });
69
71
  } catch (err) {
70
- setError(err instanceof Error ? err.message : 'Failed to fetch branches');
72
+ setError(err instanceof Error ? err.message : t('failedToFetchBranches'));
71
73
  } finally {
72
74
  setLoading(false);
73
75
  }
@@ -116,7 +118,7 @@ export function BranchCheckoutModal({
116
118
 
117
119
  <Command className="rounded-t-none border-t">
118
120
  <CommandInput
119
- placeholder="Search branches..."
121
+ placeholder={t('searchBranches')}
120
122
  value={search}
121
123
  onValueChange={setSearch}
122
124
  />
@@ -138,7 +140,7 @@ export function BranchCheckoutModal({
138
140
  ) : (
139
141
  <>
140
142
  {filteredLocalBranches.length === 0 && filteredRemoteBranches.length === 0 ? (
141
- <CommandEmpty>No branches found</CommandEmpty>
143
+ <CommandEmpty>{t('noBranches')}</CommandEmpty>
142
144
  ) : (
143
145
  <>
144
146
  {filteredLocalBranches.length > 0 && (
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useState, useCallback } from 'react';
4
4
  import { Loader2, Copy, Check, FileIcon, FilePlus, FileMinus, ArrowLeft, GitBranch, GitCommit, AlertCircle } from 'lucide-react';
5
+ import { useTranslations } from 'next-intl';
5
6
  import {
6
7
  Dialog,
7
8
  DialogContent,
@@ -34,6 +35,7 @@ export function CommitDetailsModal({
34
35
  commitHash,
35
36
  projectPath,
36
37
  }: CommitDetailsModalProps) {
38
+ const t = useTranslations('git');
37
39
  const [details, setDetails] = useState<CommitDetails | null>(null);
38
40
  const [loading, setLoading] = useState(false);
39
41
  const [error, setError] = useState<string | null>(null);
@@ -210,7 +212,7 @@ export function CommitDetailsModal({
210
212
  <button
211
213
  onClick={copyHash}
212
214
  className="p-1 hover:bg-accent rounded transition-colors"
213
- title="Copy full hash"
215
+ title={t('copyFullHash')}
214
216
  >
215
217
  {copied ? (
216
218
  <Check className="size-3.5 text-green-500" />
@@ -228,7 +230,7 @@ export function CommitDetailsModal({
228
230
  className="h-7 text-xs"
229
231
  onClick={handleCheckout}
230
232
  disabled={checkoutLoading}
231
- title="Checkout this commit (detached HEAD state)"
233
+ title={t('checkoutCommit')}
232
234
  >
233
235
  {checkoutLoading ? (
234
236
  <Loader2 className="size-3.5 animate-spin mr-1.5" />
@@ -242,7 +244,7 @@ export function CommitDetailsModal({
242
244
  size="sm"
243
245
  className="h-7 text-xs"
244
246
  onClick={() => setShowBranchDialog(true)}
245
- title="Create a new branch from this commit"
247
+ title={t('createBranchFromCommit')}
246
248
  >
247
249
  <GitBranch className="size-3.5 mr-1.5" />
248
250
  New Branch
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useRef } from 'react';
4
4
  import { X } from 'lucide-react';
5
+ import { useTranslations } from 'next-intl';
5
6
  import { Button } from '@/components/ui/button';
6
7
  import { ResizeHandle } from '@/components/ui/resize-handle';
7
8
  import { DiffViewer } from './diff-viewer';
@@ -13,6 +14,7 @@ import { cn } from '@/lib/utils';
13
14
  const { minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH } = PANEL_CONFIGS.diffPreview;
14
15
 
15
16
  export function DiffTabsPanel() {
17
+ const t = useTranslations('git');
16
18
  const {
17
19
  diffTabs,
18
20
  activeDiffTabId,
@@ -115,7 +117,7 @@ export function DiffTabsPanel() {
115
117
  size="sm"
116
118
  onClick={closeAllDiffTabs}
117
119
  className="text-xs text-muted-foreground h-8 px-2 mr-1"
118
- title="Close all tabs"
120
+ title={t('closeAllTabs')}
119
121
  >
120
122
  Close all
121
123
  </Button>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Plus, Minus, Undo2, EyeOff } from 'lucide-react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { cn } from '@/lib/utils';
5
6
  import { FileIcon } from '@/components/sidebar/file-browser/file-icon';
6
7
  import type { GitFileStatus } from '@/types';
@@ -50,6 +51,7 @@ export function GitFileItem({
50
51
  onDiscard,
51
52
  onAddToGitignore,
52
53
  }: GitFileItemProps) {
54
+ const t = useTranslations('git');
53
55
  // Get filename and parent directory
54
56
  const parts = file.path.split('/');
55
57
  const fileName = parts.pop() || file.path;
@@ -90,7 +92,7 @@ export function GitFileItem({
90
92
  e.stopPropagation();
91
93
  onAddToGitignore();
92
94
  }}
93
- title="Add to .gitignore"
95
+ title={t('addToGitignore')}
94
96
  >
95
97
  <EyeOff className="size-3.5" />
96
98
  </button>
@@ -102,7 +104,7 @@ export function GitFileItem({
102
104
  e.stopPropagation();
103
105
  onUnstage?.();
104
106
  }}
105
- title="Unstage Changes"
107
+ title={t('unstageChanges')}
106
108
  >
107
109
  <Minus className="size-3.5" />
108
110
  </button>
@@ -116,7 +118,7 @@ export function GitFileItem({
116
118
  e.stopPropagation();
117
119
  onDiscard?.();
118
120
  }}
119
- title="Discard Changes"
121
+ title={t('discardChanges')}
120
122
  >
121
123
  <Undo2 className="size-3.5" />
122
124
  </button>
@@ -128,7 +130,7 @@ export function GitFileItem({
128
130
  e.stopPropagation();
129
131
  onAddToGitignore();
130
132
  }}
131
- title="Add to .gitignore"
133
+ title={t('addToGitignore')}
132
134
  >
133
135
  <EyeOff className="size-3.5" />
134
136
  </button>
@@ -140,7 +142,7 @@ export function GitFileItem({
140
142
  e.stopPropagation();
141
143
  onStage?.();
142
144
  }}
143
- title="Stage Changes"
145
+ title={t('stageChanges')}
144
146
  >
145
147
  <Plus className="size-3.5" />
146
148
  </button>
@@ -151,7 +153,7 @@ export function GitFileItem({
151
153
  {/* Stats: +X -Y for modified, "New" for new files - absolute positioned */}
152
154
  <span className="absolute right-2 top-1/2 -translate-y-1/2 text-[11px] shrink-0 font-medium group-hover:opacity-0 transition-opacity">
153
155
  {isNew ? (
154
- <span className="text-green-500">New</span>
156
+ <span className="text-green-500">{t('newFile')}</span>
155
157
  ) : hasStats ? (
156
158
  <>
157
159
  <span className="text-green-500">+{file.additions || 0}</span>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import { ChevronRight, ChevronDown, RefreshCw, Loader2, ArrowUpFromLine, ArrowDownToLine, RotateCcw } from 'lucide-react';
5
+ import { useTranslations } from 'next-intl';
5
6
  import { GitCommitItem } from './git-commit-item';
6
7
  import { GraphRenderer } from './graph-renderer';
7
8
  import { CommitDetailsModal } from './commit-details-modal';
@@ -23,6 +24,8 @@ interface GitCommit {
23
24
  }
24
25
 
25
26
  export function GitGraph() {
27
+ const t = useTranslations('git');
28
+ const tCommon = useTranslations('common');
26
29
  const activeProject = useActiveProject();
27
30
  const [commits, setCommits] = useState<GitCommit[]>([]);
28
31
  const [head, setHead] = useState<string>('');
@@ -129,7 +132,7 @@ export function GitGraph() {
129
132
  e.stopPropagation();
130
133
  setFilter(filter === 'current' ? 'all' : 'current');
131
134
  }}
132
- title={filter === 'current' ? 'Show all branches' : 'Show current branch only'}
135
+ title={filter === 'current' ? t('showAllBranches') : t('showCurrentBranchOnly')}
133
136
  >
134
137
  <svg className="size-3.5" viewBox="0 0 16 16" fill="currentColor">
135
138
  {filter === 'current' ? (
@@ -147,7 +150,7 @@ export function GitGraph() {
147
150
  gitAction('fetch');
148
151
  }}
149
152
  disabled={actionLoading !== null}
150
- title="Fetch"
153
+ title={t('fetch')}
151
154
  >
152
155
  <ArrowDownToLine className={cn('size-3.5', actionLoading === 'fetch' && 'animate-pulse')} />
153
156
  </button>
@@ -159,7 +162,7 @@ export function GitGraph() {
159
162
  gitAction('pull');
160
163
  }}
161
164
  disabled={actionLoading !== null}
162
- title="Pull"
165
+ title={t('pull')}
163
166
  >
164
167
  <RotateCcw className={cn('size-3.5', actionLoading === 'pull' && 'animate-spin')} />
165
168
  </button>
@@ -171,7 +174,7 @@ export function GitGraph() {
171
174
  gitAction('push');
172
175
  }}
173
176
  disabled={actionLoading !== null}
174
- title="Push"
177
+ title={t('push')}
175
178
  >
176
179
  <ArrowUpFromLine className={cn('size-3.5', actionLoading === 'push' && 'animate-pulse')} />
177
180
  </button>
@@ -182,7 +185,7 @@ export function GitGraph() {
182
185
  e.stopPropagation();
183
186
  fetchLog();
184
187
  }}
185
- title="Refresh"
188
+ title={tCommon('refresh')}
186
189
  >
187
190
  <RefreshCw className={cn('size-3.5', loading && 'animate-spin')} />
188
191
  </button>
@@ -17,6 +17,7 @@ import type { GitStatus, GitFileStatus } from '@/types';
17
17
 
18
18
  export function GitPanel() {
19
19
  const t = useTranslations('git');
20
+ const tCommon = useTranslations('common');
20
21
  const activeProject = useActiveProject();
21
22
  const { openDiffTab } = useSidebarStore();
22
23
  const [status, setStatus] = useState<GitStatus | null>(null);
@@ -133,7 +134,7 @@ export function GitPanel() {
133
134
 
134
135
  const discardFile = useCallback(async (filePath: string) => {
135
136
  if (!activeProject?.path) return;
136
- if (!confirm(`Discard changes to ${filePath}?`)) return;
137
+ if (!confirm(t('discardChangesConfirm', { filePath }))) return;
137
138
  try {
138
139
  await fetch('/api/git/discard', {
139
140
  method: 'POST',
@@ -176,7 +177,7 @@ export function GitPanel() {
176
177
 
177
178
  const discardAll = useCallback(async () => {
178
179
  if (!activeProject?.path) return;
179
- if (!confirm('Discard ALL changes? This cannot be undone!')) return;
180
+ if (!confirm(t('discardAllConfirm'))) return;
180
181
  try {
181
182
  await fetch('/api/git/discard', {
182
183
  method: 'POST',
@@ -199,12 +200,12 @@ export function GitPanel() {
199
200
  });
200
201
  if (!res.ok) {
201
202
  const data = await res.json();
202
- throw new Error(data.error || 'Failed to add to .gitignore');
203
+ throw new Error(data.error || t('failedToAddGitignore'));
203
204
  }
204
205
  fetchStatus(true);
205
206
  } catch (err) {
206
207
  console.error('Failed to add to .gitignore:', err);
207
- alert(err instanceof Error ? err.message : 'Failed to add to .gitignore');
208
+ alert(err instanceof Error ? err.message : t('failedToAddGitignore'));
208
209
  }
209
210
  }, [activeProject?.path, fetchStatus]);
210
211
 
@@ -231,13 +232,13 @@ export function GitPanel() {
231
232
  });
232
233
  if (!res.ok) {
233
234
  const data = await res.json();
234
- throw new Error(data.error || 'Failed to commit');
235
+ throw new Error(data.error || t('failedToCommit'));
235
236
  }
236
237
  setCommitTitle('');
237
238
  setCommitDescription('');
238
239
  fetchStatus(true);
239
240
  } catch (err) {
240
- alert(err instanceof Error ? err.message : 'Failed to commit');
241
+ alert(err instanceof Error ? err.message : t('failedToCommit'));
241
242
  } finally {
242
243
  setCommitting(false);
243
244
  }
@@ -256,7 +257,7 @@ export function GitPanel() {
256
257
 
257
258
  if (!res.ok) {
258
259
  const data = await res.json();
259
- throw new Error(data.error || 'Failed to generate message');
260
+ throw new Error(data.error || t('failedToGenerateCommit'));
260
261
  }
261
262
 
262
263
  const { title, description } = await res.json();
@@ -264,7 +265,7 @@ export function GitPanel() {
264
265
  setCommitDescription(description || '');
265
266
  } catch (err) {
266
267
  console.error('AI generation error:', err);
267
- alert(err instanceof Error ? err.message : 'Failed to generate commit message');
268
+ alert(err instanceof Error ? err.message : t('failedToGenerateCommit'));
268
269
  } finally {
269
270
  setGeneratingMessage(false);
270
271
  }
@@ -281,11 +282,11 @@ export function GitPanel() {
281
282
  });
282
283
  if (!res.ok) {
283
284
  const data = await res.json();
284
- throw new Error(data.error || 'Failed to push');
285
+ throw new Error(data.error || t('failedToPush'));
285
286
  }
286
287
  fetchStatus(true);
287
288
  } catch (err) {
288
- alert(err instanceof Error ? err.message : 'Failed to push changes');
289
+ alert(err instanceof Error ? err.message : t('failedToPush'));
289
290
  } finally {
290
291
  setSyncing(false);
291
292
  }
@@ -334,7 +335,7 @@ export function GitPanel() {
334
335
  <div className="flex flex-col items-center justify-center h-full gap-2 p-4">
335
336
  <p className="text-sm text-destructive text-center">{error}</p>
336
337
  <Button variant="outline" size="sm" onClick={() => fetchStatus(true)}>
337
- Retry
338
+ {tCommon('retry')}
338
339
  </Button>
339
340
  </div>
340
341
  );
@@ -343,7 +344,7 @@ export function GitPanel() {
343
344
  if (!activeProject) {
344
345
  return (
345
346
  <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
346
- No project selected
347
+ {tCommon('noProjectsConfigured')}
347
348
  </div>
348
349
  );
349
350
  }
@@ -360,11 +361,11 @@ export function GitPanel() {
360
361
  <button
361
362
  className="flex items-center gap-1.5 min-w-0 hover:bg-accent/50 rounded-md px-1.5 py-0.5 transition-colors cursor-pointer"
362
363
  onClick={() => setBranchModalOpen(true)}
363
- title="Click to switch branches"
364
+ title={t('clickToSwitchBranches')}
364
365
  >
365
366
  <GitBranch className="size-4 shrink-0 text-muted-foreground" />
366
367
  <span className="text-sm font-medium truncate">
367
- {status?.branch || 'No branch'}
368
+ {status?.branch || t('noBranch')}
368
369
  </span>
369
370
  {status && (status.ahead > 0 || status.behind > 0) && (
370
371
  <div className="flex items-center gap-1 text-xs text-muted-foreground">
@@ -388,7 +389,7 @@ export function GitPanel() {
388
389
  size="icon-sm"
389
390
  onClick={() => fetchStatus(true)}
390
391
  disabled={loading}
391
- title="Refresh"
392
+ title={tCommon('refresh')}
392
393
  >
393
394
  <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
394
395
  </Button>
@@ -413,7 +414,7 @@ export function GitPanel() {
413
414
  ) : (
414
415
  <ChevronRight className="size-4" />
415
416
  )}
416
- <span className="flex-1">Changes</span>
417
+ <span className="flex-1">{t('changes')}</span>
417
418
 
418
419
  {/* Section action buttons */}
419
420
  <div className="flex items-center gap-0.5">
@@ -423,7 +424,7 @@ export function GitPanel() {
423
424
  e.stopPropagation();
424
425
  discardAll();
425
426
  }}
426
- title="Discard All Changes"
427
+ title={t('discardAllChanges')}
427
428
  >
428
429
  <Undo2 className="size-3.5" />
429
430
  </button>
@@ -433,7 +434,7 @@ export function GitPanel() {
433
434
  e.stopPropagation();
434
435
  stageAll();
435
436
  }}
436
- title="Stage All Changes"
437
+ title={t('stageAllChanges')}
437
438
  >
438
439
  <Plus className="size-3.5" />
439
440
  </button>
@@ -454,7 +455,7 @@ export function GitPanel() {
454
455
  <input
455
456
  type="text"
456
457
  className="w-full px-2 py-1.5 text-sm bg-muted/50 border rounded-md focus:outline-none focus:ring-1 focus:ring-ring"
457
- placeholder="Commit title"
458
+ placeholder={t('commitTitle')}
458
459
  value={commitTitle}
459
460
  onChange={(e) => setCommitTitle(e.target.value)}
460
461
  onKeyDown={(e) => {
@@ -466,7 +467,7 @@ export function GitPanel() {
466
467
  {/* Commit description textarea */}
467
468
  <textarea
468
469
  className="w-full min-h-[60px] px-2 py-1.5 text-sm bg-muted/50 border rounded-md focus:outline-none focus:ring-1 focus:ring-ring resize-y"
469
- placeholder="Description (optional)"
470
+ placeholder={t('descriptionOptional')}
470
471
  value={commitDescription}
471
472
  onChange={(e) => setCommitDescription(e.target.value)}
472
473
  onKeyDown={(e) => {
@@ -488,12 +489,12 @@ export function GitPanel() {
488
489
  ) : hasUnpushedCommits ? (
489
490
  <>
490
491
  <ArrowUp className="size-4 mr-1" />
491
- Sync changes
492
+ {t('syncChanges')}
492
493
  </>
493
494
  ) : (
494
495
  <>
495
496
  <Check className="size-4 mr-1" />
496
- Commit changes
497
+ {t('commitChanges')}
497
498
  </>
498
499
  )}
499
500
  </Button>
@@ -505,10 +506,10 @@ export function GitPanel() {
505
506
  className="px-2"
506
507
  title={
507
508
  totalChanges === 0
508
- ? 'No changes to generate commit message for'
509
+ ? t('noChangesToGenerate')
509
510
  : generatingMessage
510
- ? 'Generating...'
511
- : 'Generate commit message with AI'
511
+ ? t('generatingCommit')
512
+ : t('generateCommitMessage')
512
513
  }
513
514
  onClick={handleGenerateMessage}
514
515
  disabled={generatingMessage || totalChanges === 0}
@@ -539,7 +540,7 @@ export function GitPanel() {
539
540
  {totalChanges === 0 ? (
540
541
  <div className="flex flex-col items-center justify-center py-4 text-muted-foreground text-sm">
541
542
  <p>{t('noChanges')}</p>
542
- <p className="text-xs mt-1">Working tree clean</p>
543
+ <p className="text-xs mt-1">{t('workingTreeClean')}</p>
543
544
  </div>
544
545
  ) : (
545
546
  <>
@@ -627,7 +628,7 @@ export function GitPanel() {
627
628
  open={branchModalOpen}
628
629
  onOpenChange={setBranchModalOpen}
629
630
  projectPath={activeProject.path}
630
- currentBranch={status.branch || 'No branch'}
631
+ currentBranch={status.branch || t('noBranch')}
631
632
  onCheckout={handleBranchCheckout}
632
633
  />
633
634
  )}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import { ChevronRight, ChevronDown, Plus, Minus, Undo2 } from 'lucide-react';
5
+ import { useTranslations } from 'next-intl';
5
6
  import { GitFileItem } from './git-file-item';
6
7
  import { cn } from '@/lib/utils';
7
8
  import type { GitFileStatus } from '@/types';
@@ -35,6 +36,7 @@ export function GitSection({
35
36
  onUnstageAll,
36
37
  onDiscardAll,
37
38
  }: GitSectionProps) {
39
+ const t = useTranslations('git');
38
40
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
39
41
 
40
42
  if (files.length === 0) return null;
@@ -66,7 +68,7 @@ export function GitSection({
66
68
  e.stopPropagation();
67
69
  onUnstageAll?.();
68
70
  }}
69
- title="Unstage All Changes"
71
+ title={t('unstageAllChanges')}
70
72
  >
71
73
  <Minus className="size-3.5" />
72
74
  </button>
@@ -79,7 +81,7 @@ export function GitSection({
79
81
  e.stopPropagation();
80
82
  onDiscardAll?.();
81
83
  }}
82
- title="Discard All Changes"
84
+ title={t('discardAllChanges')}
83
85
  >
84
86
  <Undo2 className="size-3.5" />
85
87
  </button>
@@ -90,7 +92,7 @@ export function GitSection({
90
92
  e.stopPropagation();
91
93
  onStageAll?.();
92
94
  }}
93
- title="Stage All Changes"
95
+ title={t('stageAllChanges')}
94
96
  >
95
97
  <Plus className="size-3.5" />
96
98
  </button>
@@ -5,6 +5,7 @@ import { Square, Terminal, Circle } from 'lucide-react';
5
5
  import { Button } from '@/components/ui/button';
6
6
  import { cn } from '@/lib/utils';
7
7
  import { useShellStore, type ShellInfo } from '@/stores/shell-store';
8
+ import { useTranslations } from 'next-intl';
8
9
 
9
10
  interface ShellPanelProps {
10
11
  projectId: string;
@@ -12,6 +13,7 @@ interface ShellPanelProps {
12
13
  }
13
14
 
14
15
  function ShellItem({ shell, onStop }: { shell: ShellInfo; onStop: () => void }) {
16
+ const t = useTranslations('shells');
15
17
  // Truncate command for display
16
18
  const displayCommand = shell.command.length > 30
17
19
  ? shell.command.slice(0, 30) + '...'
@@ -48,7 +50,7 @@ function ShellItem({ shell, onStop }: { shell: ShellInfo; onStop: () => void })
48
50
  size="icon"
49
51
  className="h-6 w-6 flex-shrink-0"
50
52
  onClick={onStop}
51
- title="Stop shell"
53
+ title={t('stopShell')}
52
54
  >
53
55
  <Square className="h-3 w-3" />
54
56
  </Button>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Plus } from 'lucide-react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { FilePreview } from './file-preview';
5
6
  import { Button } from '@/components/ui/button';
6
7
  import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
@@ -19,6 +20,8 @@ export function AttachmentBar({
19
20
  onRetry,
20
21
  onAddFiles,
21
22
  }: AttachmentBarProps) {
23
+ const t = useTranslations('editor');
24
+
22
25
  if (files.length === 0) return null;
23
26
 
24
27
  return (
@@ -42,7 +45,7 @@ export function AttachmentBar({
42
45
  variant="ghost"
43
46
  size="icon"
44
47
  onClick={onAddFiles}
45
- title="Add more files"
48
+ title={t('addMoreFiles')}
46
49
  type="button"
47
50
  className="shrink-0"
48
51
  >
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { formatDistanceToNow as formatDateDistance } from 'date-fns';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { Badge } from '@/components/ui/badge';
5
6
  import { cn } from '@/lib/utils';
6
7
  import type { Attempt } from '@/types';
@@ -13,13 +14,14 @@ interface AttemptItemProps {
13
14
  }
14
15
 
15
16
  const STATUS_VARIANTS = {
16
- running: { variant: 'secondary' as const, label: 'Running', color: 'text-yellow-600' },
17
- completed: { variant: 'secondary' as const, label: 'Completed', color: 'text-green-600' },
18
- failed: { variant: 'destructive' as const, label: 'Failed', color: 'text-red-600' },
19
- cancelled: { variant: 'outline' as const, label: 'Cancelled', color: 'text-muted-foreground' },
17
+ running: { variant: 'secondary' as const, labelKey: 'statusRunning' as const, color: 'text-yellow-600' },
18
+ completed: { variant: 'secondary' as const, labelKey: 'statusCompleted' as const, color: 'text-green-600' },
19
+ failed: { variant: 'destructive' as const, labelKey: 'statusFailed' as const, color: 'text-red-600' },
20
+ cancelled: { variant: 'outline' as const, labelKey: 'statusCancelled' as const, color: 'text-muted-foreground' },
20
21
  };
21
22
 
22
23
  export function AttemptItem({ attempt, onClick, isActive, className }: AttemptItemProps) {
24
+ const t = useTranslations('task');
23
25
  const statusConfig = STATUS_VARIANTS[attempt.status];
24
26
  const hasDiff = attempt.diffAdditions > 0 || attempt.diffDeletions > 0;
25
27
 
@@ -38,7 +40,7 @@ export function AttemptItem({ attempt, onClick, isActive, className }: AttemptIt
38
40
  #{attempt.id.slice(0, 8)}
39
41
  </span>
40
42
  <Badge variant={statusConfig.variant} className={cn('text-xs', statusConfig.color)}>
41
- {statusConfig.label}
43
+ {t(statusConfig.labelKey)}
42
44
  </Badge>
43
45
  </div>
44
46
  <span className="text-xs text-muted-foreground whitespace-nowrap">