@cat-factory/app 0.46.12 → 0.47.0

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 (27) hide show
  1. package/app/components/auth/AuthGate.vue +1 -1
  2. package/app/components/auth/LoginScreen.vue +131 -1
  3. package/app/components/layout/AccountDeploymentSettings.vue +232 -5
  4. package/app/components/providers/ApiKeysSection.vue +27 -3
  5. package/app/components/providers/PersonalSubscriptionSection.vue +17 -2
  6. package/app/components/providers/SignInRequiredNotice.vue +19 -0
  7. package/app/components/settings/AccountSettingsPanel.vue +9 -6
  8. package/app/components/settings/IssueTrackerPanel.vue +114 -53
  9. package/app/components/settings/LocalModeSettingsPanel.vue +60 -28
  10. package/app/components/settings/LocalModelEndpointsPanel.vue +72 -27
  11. package/app/components/settings/MergeThresholdsPanel.vue +93 -45
  12. package/app/components/settings/ModelConfigurationPanel.vue +62 -36
  13. package/app/components/settings/ObservabilityConnectionPanel.vue +65 -35
  14. package/app/components/settings/OpenRouterCatalogPanel.vue +70 -40
  15. package/app/components/settings/ProviderConnectionPanel.vue +115 -61
  16. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +9 -12
  17. package/app/components/settings/UserSecretsSection.vue +37 -18
  18. package/app/components/settings/WorkspaceSettingsPanel.vue +114 -62
  19. package/app/composables/api/auth.ts +7 -0
  20. package/app/stores/auth.ts +25 -1
  21. package/app/types/accountSettings.ts +4 -0
  22. package/i18n/locales/en.json +496 -0
  23. package/i18n/locales/es.json +490 -0
  24. package/i18n/locales/fr.json +490 -0
  25. package/i18n/locales/pl.json +490 -0
  26. package/i18n/locales/uk.json +490 -0
  27. package/package.json +2 -2
@@ -17,6 +17,7 @@
17
17
  import { computed, onMounted, ref, watch } from 'vue'
18
18
  import type { TaskSourceDiagnosticStatus, TaskSourceKind, TrackerKind } from '~/types/domain'
19
19
 
20
+ const { t } = useI18n()
20
21
  const tracker = useTrackerStore()
21
22
  const tasks = useTasksStore()
22
23
  const ui = useUiStore()
@@ -78,10 +79,14 @@ async function save() {
78
79
  writebackCommentOnPrOpen: commentOnPrOpen.value,
79
80
  writebackResolveOnMerge: resolveOnMerge.value,
80
81
  })
81
- toast.add({ title: 'Issue tracker saved', icon: 'i-lucide-check', color: 'success' })
82
+ toast.add({
83
+ title: t('settings.issueTracker.toast.saved'),
84
+ icon: 'i-lucide-check',
85
+ color: 'success',
86
+ })
82
87
  } catch (e) {
83
88
  toast.add({
84
- title: 'Could not save settings',
89
+ title: t('settings.issueTracker.toast.saveFailed'),
85
90
  description: e instanceof Error ? e.message : String(e),
86
91
  icon: 'i-lucide-triangle-alert',
87
92
  color: 'error',
@@ -100,7 +105,7 @@ async function toggleSource(source: TaskSourceKind, enabled: boolean) {
100
105
  await tasks.setEnabled(source, enabled)
101
106
  } catch (e) {
102
107
  toast.add({
103
- title: 'Could not update',
108
+ title: t('settings.issueTracker.toast.updateFailed'),
104
109
  description: e instanceof Error ? e.message : String(e),
105
110
  icon: 'i-lucide-triangle-alert',
106
111
  color: 'error',
@@ -118,12 +123,20 @@ const probeFailureHint = computed(() => {
118
123
  const err = tasks.probeError
119
124
  if (tasks.available !== false || !err) return null
120
125
  if (err.status === 503) {
121
- return 'The task-source integration is turned off on this deployment (its encryption key is not configured). Set ENCRYPTION_KEY on the backend to enable issue tracking.'
126
+ return t('settings.issueTracker.probeFailure.disabled')
122
127
  }
123
128
  if (err.status && err.status >= 500) {
124
- return `The issue-tracker service returned an error (HTTP ${err.status}): ${err.message}. This usually means the backend isn't fully migrated/configured.`
129
+ return t('settings.issueTracker.probeFailure.serverError', {
130
+ status: err.status,
131
+ message: err.message,
132
+ })
125
133
  }
126
- return `Couldn't load issue-tracker settings${err.status ? ` (HTTP ${err.status})` : ''}: ${err.message}`
134
+ return err.status
135
+ ? t('settings.issueTracker.probeFailure.loadFailedStatus', {
136
+ status: err.status,
137
+ message: err.message,
138
+ })
139
+ : t('settings.issueTracker.probeFailure.loadFailed', { message: err.message })
127
140
  })
128
141
 
129
142
  async function checkSetup(source: TaskSourceKind) {
@@ -131,7 +144,7 @@ async function checkSetup(source: TaskSourceKind) {
131
144
  await tasks.checkSetup(source)
132
145
  } catch (e) {
133
146
  toast.add({
134
- title: 'Check failed',
147
+ title: t('settings.issueTracker.toast.checkFailed'),
135
148
  description: e instanceof Error ? e.message : String(e),
136
149
  icon: 'i-lucide-triangle-alert',
137
150
  color: 'error',
@@ -163,17 +176,22 @@ const STATUS_UI: Record<
163
176
  color="error"
164
177
  variant="subtle"
165
178
  icon="i-lucide-triangle-alert"
166
- title="Issue tracking isn't available"
179
+ :title="t('settings.issueTracker.probeFailure.title')"
167
180
  :description="probeFailureHint"
168
181
  />
169
182
 
170
183
  <!-- 1. Filing tracker ----------------------------------------------------->
171
184
  <section class="space-y-3">
172
185
  <div>
173
- <h3 class="text-sm font-semibold text-slate-200">Where tickets are filed</h3>
186
+ <h3 class="text-sm font-semibold text-slate-200">
187
+ {{ t('settings.issueTracker.filing.heading') }}
188
+ </h3>
174
189
  <p class="mt-1 text-[11px] text-slate-400">
175
- The tech-debt recurring pipeline raises an issue before implementation and files it in
176
- this tracker. Choose <span class="text-slate-300">None</span> to skip filing.
190
+ <i18n-t keypath="settings.issueTracker.filing.description" tag="span" scope="global">
191
+ <template #none>
192
+ <span class="text-slate-300">{{ t('settings.issueTracker.filing.none') }}</span>
193
+ </template>
194
+ </i18n-t>
177
195
  </p>
178
196
  </div>
179
197
 
@@ -185,7 +203,7 @@ const STATUS_UI: Record<
185
203
  :variant="trackerKind === null ? 'solid' : 'subtle'"
186
204
  @click="trackerKind = null"
187
205
  >
188
- None
206
+ {{ t('settings.issueTracker.filing.none') }}
189
207
  </UButton>
190
208
  <UButton
191
209
  icon="i-lucide-github"
@@ -194,7 +212,7 @@ const STATUS_UI: Record<
194
212
  :variant="trackerKind === 'github' ? 'solid' : 'subtle'"
195
213
  @click="trackerKind = 'github'"
196
214
  >
197
- GitHub Issues
215
+ {{ t('settings.issueTracker.vendor.github') }}
198
216
  </UButton>
199
217
  <UButton
200
218
  icon="i-lucide-trello"
@@ -218,36 +236,58 @@ const STATUS_UI: Record<
218
236
 
219
237
  <!-- Inline readiness hints for the picked tracker. -->
220
238
  <p v-if="trackerKind === 'github' && !githubAvailable" class="text-[11px] text-amber-400">
221
- GitHub Issues rides your installed GitHub App, which isn't connected yet. Install it under
222
- <button class="underline" @click="ui.openGitHub()">Integrations → GitHub</button> — filing
223
- stays off until then.
239
+ <i18n-t keypath="settings.issueTracker.filing.githubHint" tag="span" scope="global">
240
+ <template #link>
241
+ <button class="underline" @click="ui.openGitHub()">
242
+ {{ t('settings.issueTracker.filing.githubHintLink') }}
243
+ </button>
244
+ </template>
245
+ </i18n-t>
224
246
  </p>
225
247
  <p v-else-if="trackerKind === 'jira' && !jiraConnected" class="text-[11px] text-amber-400">
226
- Jira isn't connected yet.
227
- <button class="underline" @click="ui.openTaskConnect('jira')">Connect it</button> to file
228
- and link issues.
248
+ <i18n-t keypath="settings.issueTracker.filing.jiraHint" tag="span" scope="global">
249
+ <template #link>
250
+ <button class="underline" @click="ui.openTaskConnect('jira')">
251
+ {{ t('settings.issueTracker.filing.connectLink') }}
252
+ </button>
253
+ </template>
254
+ </i18n-t>
229
255
  </p>
230
256
  <p
231
257
  v-else-if="trackerKind === 'linear' && !linearConnected"
232
258
  class="text-[11px] text-amber-400"
233
259
  >
234
- Linear isn't connected yet.
235
- <button class="underline" @click="ui.openTaskConnect('linear')">Connect it</button> to file
236
- and link issues.
260
+ <i18n-t keypath="settings.issueTracker.filing.linearHint" tag="span" scope="global">
261
+ <template #link>
262
+ <button class="underline" @click="ui.openTaskConnect('linear')">
263
+ {{ t('settings.issueTracker.filing.connectLink') }}
264
+ </button>
265
+ </template>
266
+ </i18n-t>
237
267
  </p>
238
268
 
239
- <UFormField v-if="trackerKind === 'jira'" label="Jira project key" class="w-48">
269
+ <UFormField
270
+ v-if="trackerKind === 'jira'"
271
+ :label="t('settings.issueTracker.filing.jiraProjectKey')"
272
+ class="w-48"
273
+ >
240
274
  <UInput v-model="jiraProjectKey" placeholder="ENG" size="sm" class="w-full" />
241
275
  <template #help>
242
- <span class="text-[11px] text-slate-500">New tickets are filed under this project.</span>
276
+ <span class="text-[11px] text-slate-500">
277
+ {{ t('settings.issueTracker.filing.jiraProjectKeyHelp') }}
278
+ </span>
243
279
  </template>
244
280
  </UFormField>
245
281
 
246
- <UFormField v-if="trackerKind === 'linear'" label="Linear team id" class="w-64">
282
+ <UFormField
283
+ v-if="trackerKind === 'linear'"
284
+ :label="t('settings.issueTracker.filing.linearTeamId')"
285
+ class="w-64"
286
+ >
247
287
  <UInput v-model="linearTeamId" placeholder="team_…" size="sm" class="w-full" />
248
288
  <template #help>
249
289
  <span class="text-[11px] text-slate-500">
250
- New issues are created under this team (Linear requires a team to create an issue).
290
+ {{ t('settings.issueTracker.filing.linearTeamIdHelp') }}
251
291
  </span>
252
292
  </template>
253
293
  </UFormField>
@@ -256,11 +296,11 @@ const STATUS_UI: Record<
256
296
  <!-- 2. Linking sources ---------------------------------------------------->
257
297
  <section class="space-y-3">
258
298
  <div>
259
- <h3 class="text-sm font-semibold text-slate-200">Link issues as context</h3>
299
+ <h3 class="text-sm font-semibold text-slate-200">
300
+ {{ t('settings.issueTracker.linking.heading') }}
301
+ </h3>
260
302
  <p class="mt-1 text-[11px] text-slate-400">
261
- When a source is offered you can import its issues and attach them to a task, so agents
262
- see the issue description and comments while implementing. This is independent of the
263
- filing tracker above.
303
+ {{ t('settings.issueTracker.linking.description') }}
264
304
  </p>
265
305
  </div>
266
306
 
@@ -270,12 +310,14 @@ const STATUS_UI: Record<
270
310
  <div class="flex min-w-0 items-center gap-2.5">
271
311
  <UIcon name="i-lucide-github" class="h-5 w-5 shrink-0 text-slate-300" />
272
312
  <div class="min-w-0">
273
- <div class="text-sm font-medium text-slate-200">GitHub Issues</div>
313
+ <div class="text-sm font-medium text-slate-200">
314
+ {{ t('settings.issueTracker.vendor.github') }}
315
+ </div>
274
316
  <div class="text-[11px] text-slate-500">
275
317
  {{
276
318
  githubAvailable
277
- ? 'Rides your installed GitHub App — no credentials needed.'
278
- : 'Install the GitHub App (Integrations → GitHub) to offer this source.'
319
+ ? t('settings.issueTracker.linking.github.available')
320
+ : t('settings.issueTracker.linking.github.unavailable')
279
321
  }}
280
322
  </div>
281
323
  </div>
@@ -289,7 +331,7 @@ const STATUS_UI: Record<
289
331
  :loading="tasks.checking === 'github'"
290
332
  @click="checkSetup('github')"
291
333
  >
292
- Check setup
334
+ {{ t('settings.issueTracker.linking.checkSetup') }}
293
335
  </UButton>
294
336
  <USwitch
295
337
  v-if="githubAvailable"
@@ -305,7 +347,7 @@ const STATUS_UI: Record<
305
347
  icon="i-lucide-github"
306
348
  @click="ui.openGitHub()"
307
349
  >
308
- Install
350
+ {{ t('settings.issueTracker.linking.install') }}
309
351
  </UButton>
310
352
  </div>
311
353
  </div>
@@ -329,9 +371,15 @@ const STATUS_UI: Record<
329
371
  <div class="flex min-w-0 items-center gap-2.5">
330
372
  <UIcon name="i-lucide-trello" class="h-5 w-5 shrink-0 text-slate-300" />
331
373
  <div class="min-w-0">
332
- <div class="text-sm font-medium text-slate-200">Jira</div>
374
+ <div class="text-sm font-medium text-slate-200">
375
+ {{ t('settings.issueTracker.vendor.jira') }}
376
+ </div>
333
377
  <div class="text-[11px] text-slate-500">
334
- {{ jiraConnected ? 'Connected.' : 'Connect with a Jira account and API token.' }}
378
+ {{
379
+ jiraConnected
380
+ ? t('settings.issueTracker.linking.connected')
381
+ : t('settings.issueTracker.linking.jira.connectHint')
382
+ }}
335
383
  </div>
336
384
  </div>
337
385
  </div>
@@ -345,7 +393,7 @@ const STATUS_UI: Record<
345
393
  :loading="tasks.checking === 'jira'"
346
394
  @click="checkSetup('jira')"
347
395
  >
348
- Check setup
396
+ {{ t('settings.issueTracker.linking.checkSetup') }}
349
397
  </UButton>
350
398
  <USwitch
351
399
  v-if="jira?.available"
@@ -361,7 +409,7 @@ const STATUS_UI: Record<
361
409
  icon="i-lucide-plug"
362
410
  @click="ui.openTaskConnect('jira')"
363
411
  >
364
- Connect
412
+ {{ t('settings.issueTracker.linking.connect') }}
365
413
  </UButton>
366
414
  </div>
367
415
  </div>
@@ -385,9 +433,15 @@ const STATUS_UI: Record<
385
433
  <div class="flex min-w-0 items-center gap-2.5">
386
434
  <UIcon name="i-lucide-square-kanban" class="h-5 w-5 shrink-0 text-slate-300" />
387
435
  <div class="min-w-0">
388
- <div class="text-sm font-medium text-slate-200">Linear</div>
436
+ <div class="text-sm font-medium text-slate-200">
437
+ {{ t('settings.issueTracker.vendor.linear') }}
438
+ </div>
389
439
  <div class="text-[11px] text-slate-500">
390
- {{ linearConnected ? 'Connected.' : 'Connect with a Linear personal API key.' }}
440
+ {{
441
+ linearConnected
442
+ ? t('settings.issueTracker.linking.connected')
443
+ : t('settings.issueTracker.linking.linear.connectHint')
444
+ }}
391
445
  </div>
392
446
  </div>
393
447
  </div>
@@ -401,7 +455,7 @@ const STATUS_UI: Record<
401
455
  :loading="tasks.checking === 'linear'"
402
456
  @click="checkSetup('linear')"
403
457
  >
404
- Check setup
458
+ {{ t('settings.issueTracker.linking.checkSetup') }}
405
459
  </UButton>
406
460
  <USwitch
407
461
  v-if="linear?.available"
@@ -417,7 +471,7 @@ const STATUS_UI: Record<
417
471
  icon="i-lucide-plug"
418
472
  @click="ui.openTaskConnect('linear')"
419
473
  >
420
- Connect
474
+ {{ t('settings.issueTracker.linking.connect') }}
421
475
  </UButton>
422
476
  </div>
423
477
  </div>
@@ -439,21 +493,26 @@ const STATUS_UI: Record<
439
493
  <!-- 3. Writeback ---------------------------------------------------------->
440
494
  <section class="space-y-3">
441
495
  <div>
442
- <h3 class="text-sm font-semibold text-slate-200">Writeback</h3>
496
+ <h3 class="text-sm font-semibold text-slate-200">
497
+ {{ t('settings.issueTracker.writeback.heading') }}
498
+ </h3>
443
499
  <p class="mt-1 text-[11px] text-slate-400">
444
- Write back to a task's linked issue(s) as its pull request progresses. Each toggle is the
445
- workspace default and can be overridden per task in the inspector. GitHub issues close
446
- natively; Jira issues transition to the first status in their
447
- <span class="text-slate-300">Done</span> category.
500
+ <i18n-t keypath="settings.issueTracker.writeback.description" tag="span" scope="global">
501
+ <template #done>
502
+ <span class="text-slate-300">{{ t('settings.issueTracker.writeback.done') }}</span>
503
+ </template>
504
+ </i18n-t>
448
505
  </p>
449
506
  </div>
450
507
 
451
508
  <label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
452
509
  <USwitch v-model="commentOnPrOpen" />
453
510
  <span class="text-sm">
454
- <span class="block text-slate-200">Comment when a PR opens</span>
511
+ <span class="block text-slate-200">
512
+ {{ t('settings.issueTracker.writeback.commentOnOpen.label') }}
513
+ </span>
455
514
  <span class="block text-xs text-slate-500">
456
- Post a comment on the linked issue with the new pull request's link.
515
+ {{ t('settings.issueTracker.writeback.commentOnOpen.help') }}
457
516
  </span>
458
517
  </span>
459
518
  </label>
@@ -461,9 +520,11 @@ const STATUS_UI: Record<
461
520
  <label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
462
521
  <USwitch v-model="resolveOnMerge" />
463
522
  <span class="text-sm">
464
- <span class="block text-slate-200">Close as resolved when a PR merges</span>
523
+ <span class="block text-slate-200">
524
+ {{ t('settings.issueTracker.writeback.resolveOnMerge.label') }}
525
+ </span>
465
526
  <span class="block text-xs text-slate-500">
466
- Comment that the PR merged, then close / resolve the linked issue.
527
+ {{ t('settings.issueTracker.writeback.resolveOnMerge.help') }}
467
528
  </span>
468
529
  </span>
469
530
  </label>
@@ -478,7 +539,7 @@ const STATUS_UI: Record<
478
539
  :disabled="!canSave"
479
540
  @click="save"
480
541
  >
481
- Save
542
+ {{ t('common.save') }}
482
543
  </UButton>
483
544
  </div>
484
545
  </div>
@@ -9,6 +9,7 @@
9
9
  // started after the save.
10
10
  import { reactive, ref, watch } from 'vue'
11
11
 
12
+ const { t } = useI18n()
12
13
  const ui = useUiStore()
13
14
  const store = useLocalSettingsStore()
14
15
  const toast = useToast()
@@ -68,10 +69,14 @@ async function save() {
68
69
  },
69
70
  checkout: { workspaceRoot: draft.workspaceRoot.trim() || '/workspace', cleanKeep },
70
71
  })
71
- toast.add({ title: 'Local settings saved', icon: 'i-lucide-check', color: 'success' })
72
+ toast.add({
73
+ title: t('settings.localMode.toast.saved'),
74
+ icon: 'i-lucide-check',
75
+ color: 'success',
76
+ })
72
77
  } catch (e) {
73
78
  toast.add({
74
- title: 'Could not save local settings',
79
+ title: t('settings.localMode.toast.saveFailed'),
75
80
  description: e instanceof Error ? e.message : String(e),
76
81
  icon: 'i-lucide-triangle-alert',
77
82
  color: 'error',
@@ -83,43 +88,69 @@ async function save() {
83
88
  </script>
84
89
 
85
90
  <template>
86
- <UModal v-model:open="open" title="Local mode" :ui="{ content: 'max-w-2xl' }">
91
+ <UModal v-model:open="open" :title="t('settings.localMode.title')" :ui="{ content: 'max-w-2xl' }">
87
92
  <template #body>
88
93
  <div class="space-y-6">
89
- <p class="text-xs text-slate-400">
90
- Tuning for the local container runner — stored on this machine's deployment (it replaced
91
- the <code>LOCAL_POOL_*</code> / <code>HARNESS_*</code> env vars). Saving resizes the warm
92
- pool live — no restart needed; in-flight runs keep the container they already hold.
93
- </p>
94
+ <i18n-t
95
+ keypath="settings.localMode.intro"
96
+ tag="p"
97
+ class="text-xs text-slate-400"
98
+ scope="global"
99
+ >
100
+ <template #poolVars>
101
+ <code>LOCAL_POOL_*</code>
102
+ </template>
103
+ <template #harnessVars>
104
+ <code>HARNESS_*</code>
105
+ </template>
106
+ </i18n-t>
94
107
 
95
108
  <!-- Warm container pool -->
96
109
  <section class="space-y-3">
97
110
  <div>
98
- <h4 class="text-sm font-semibold text-slate-200">Warm container pool</h4>
99
- <p class="text-[11px] text-slate-400">
100
- Keep idle harness containers ready and re-lease one (preferring a container that
101
- already holds the run's repo) instead of cold-starting per run. Pool size 0 disables
102
- it. Requires a Docker-family runtime (Docker/Podman/OrbStack/Colima); ignored on Apple
103
- <code>container</code>.
104
- </p>
111
+ <h4 class="text-sm font-semibold text-slate-200">
112
+ {{ t('settings.localMode.pool.heading') }}
113
+ </h4>
114
+ <i18n-t
115
+ keypath="settings.localMode.pool.description"
116
+ tag="p"
117
+ class="text-[11px] text-slate-400"
118
+ scope="global"
119
+ >
120
+ <template #appleContainer>
121
+ <code>container</code>
122
+ </template>
123
+ </i18n-t>
105
124
  </div>
106
125
  <div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
107
- <UFormField label="Pool size" help="Max idle warm containers. 0 = pooling off.">
126
+ <UFormField
127
+ :label="t('settings.localMode.pool.size.label')"
128
+ :help="t('settings.localMode.pool.size.help')"
129
+ >
108
130
  <UInput v-model.number="draft.size" type="number" :min="0" size="sm" />
109
131
  </UFormField>
110
- <UFormField label="Pre-warm at boot" help="Containers started when the service boots.">
132
+ <UFormField
133
+ :label="t('settings.localMode.pool.minWarm.label')"
134
+ :help="t('settings.localMode.pool.minWarm.help')"
135
+ >
111
136
  <UInput v-model.number="draft.minWarm" type="number" :min="0" size="sm" />
112
137
  </UFormField>
113
- <UFormField label="Max containers" help="Hard cap (leased + idle). Blank = pool size.">
138
+ <UFormField
139
+ :label="t('settings.localMode.pool.max.label')"
140
+ :help="t('settings.localMode.pool.max.help')"
141
+ >
114
142
  <UInput
115
143
  v-model.number="draft.max"
116
144
  type="number"
117
145
  :min="0"
118
146
  size="sm"
119
- placeholder="(pool size)"
147
+ :placeholder="t('settings.localMode.pool.max.placeholder')"
120
148
  />
121
149
  </UFormField>
122
- <UFormField label="Idle timeout (minutes)" help="Evict an idle pooled container after.">
150
+ <UFormField
151
+ :label="t('settings.localMode.pool.idleTimeout.label')"
152
+ :help="t('settings.localMode.pool.idleTimeout.help')"
153
+ >
123
154
  <UInput v-model.number="draft.idleMinutes" type="number" :min="0" size="sm" />
124
155
  </UFormField>
125
156
  </div>
@@ -128,21 +159,22 @@ async function save() {
128
159
  <!-- Checkout reuse -->
129
160
  <section class="space-y-3 border-t border-slate-800 pt-6">
130
161
  <div>
131
- <h4 class="text-sm font-semibold text-slate-200">Checkout reuse</h4>
162
+ <h4 class="text-sm font-semibold text-slate-200">
163
+ {{ t('settings.localMode.checkout.heading') }}
164
+ </h4>
132
165
  <p class="text-[11px] text-slate-400">
133
- When a warm container already holds the run's repo, the harness reuses its per-repo
134
- checkout (clean sweep + fetch + switch branch) instead of cloning fresh.
166
+ {{ t('settings.localMode.checkout.description') }}
135
167
  </p>
136
168
  </div>
137
169
  <UFormField
138
- label="Workspace root"
139
- help="Absolute in-container directory the reused checkout lives under."
170
+ :label="t('settings.localMode.checkout.workspaceRoot.label')"
171
+ :help="t('settings.localMode.checkout.workspaceRoot.help')"
140
172
  >
141
173
  <UInput v-model="draft.workspaceRoot" size="sm" placeholder="/workspace" />
142
174
  </UFormField>
143
175
  <UFormField
144
- label="Keep on clean (comma-separated)"
145
- help="Dependency-cache directories the per-run clean sweep preserves."
176
+ :label="t('settings.localMode.checkout.cleanKeep.label')"
177
+ :help="t('settings.localMode.checkout.cleanKeep.help')"
146
178
  >
147
179
  <UInput
148
180
  v-model="draft.cleanKeep"
@@ -154,7 +186,7 @@ async function save() {
154
186
 
155
187
  <div class="flex justify-end">
156
188
  <UButton color="primary" icon="i-lucide-save" :loading="saving" @click="save">
157
- Save
189
+ {{ t('common.save') }}
158
190
  </UButton>
159
191
  </div>
160
192
  </div>