@cat-factory/app 0.46.8 → 0.46.10

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.
@@ -13,6 +13,7 @@ const ui = useUiStore()
13
13
  const agentRuns = useAgentRunsStore()
14
14
  const reviews = useReviewStage()
15
15
  const toast = useToast()
16
+ const { t } = useI18n()
16
17
 
17
18
  const task = computed<Block | undefined>(() => board.getBlock(props.taskId))
18
19
  const statusMeta = computed(() => (task.value ? STATUS_META[task.value.status] : null))
@@ -43,7 +44,9 @@ const defaultPipeline = computed(
43
44
 
44
45
  /** The PR the implementer agent opened for this task, if any. */
45
46
  const pr = computed(() => task.value?.pullRequest)
46
- const prLabel = computed(() => (pr.value?.number ? `PR #${pr.value.number}` : 'PR'))
47
+ const prLabel = computed(() =>
48
+ pr.value?.number ? t('board.task.prNumber', { number: pr.value.number }) : t('board.task.pr'),
49
+ )
47
50
 
48
51
  // This task's current agent run (if any). A failed run must surface the shared
49
52
  // failure banner + retry — NOT a stuck progress bar — so the card never looks
@@ -65,15 +68,18 @@ const starting = ref(false)
65
68
  async function run() {
66
69
  if (!runnable.value) {
67
70
  toast.add({
68
- title: 'Blocked by dependencies',
69
- description: `Waiting on: ${unmet.value.map((d) => d.title).join(', ')}`,
71
+ title: t('board.task.blockedByDependenciesTitle'),
72
+ description: t('board.task.waitingOn', { deps: unmet.value.map((d) => d.title).join(', ') }),
70
73
  icon: 'i-lucide-lock',
71
74
  })
72
75
  return
73
76
  }
74
77
  const pipeline = defaultPipeline.value
75
78
  if (!pipeline) {
76
- toast.add({ title: 'No pipeline defined', description: 'Create one in the builder first.' })
79
+ toast.add({
80
+ title: t('board.task.noPipelineTitle'),
81
+ description: t('board.task.noPipelineBody'),
82
+ })
77
83
  return
78
84
  }
79
85
  starting.value = true
@@ -108,11 +114,11 @@ const pendingDecision = computed(() =>
108
114
  const reviewStage = computed(() => reviews.stageForBlock(props.taskId))
109
115
  const reviewStageLabel = computed(() =>
110
116
  reviewStage.value === 'incorporating'
111
- ? 'Incorporating answers…'
117
+ ? t('board.task.incorporatingAnswers')
112
118
  : reviewStage.value === 'reviewing'
113
- ? 'Re-reviewing…'
119
+ ? t('board.task.reReviewing')
114
120
  : reviewStage.value === 'recommending'
115
- ? 'Recommending…'
121
+ ? t('board.task.recommending')
116
122
  : null,
117
123
  )
118
124
  const pendingApproval = computed(() => {
@@ -135,17 +141,17 @@ const attention = computed<{
135
141
  const d = pendingDecision.value
136
142
  if (d)
137
143
  return {
138
- label: 'Decision needed',
144
+ label: t('board.task.decisionNeeded'),
139
145
  icon: 'i-lucide-circle-help',
140
- action: 'Resolve',
146
+ action: t('board.task.resolve'),
141
147
  open: () => ui.openDecision(d.instanceId, d.decision.id),
142
148
  }
143
149
  const a = pendingApproval.value
144
150
  if (a)
145
151
  return {
146
- label: 'Approval needed',
152
+ label: t('board.task.approvalNeeded'),
147
153
  icon: 'i-lucide-shield-check',
148
- action: 'Approve',
154
+ action: t('board.task.approve'),
149
155
  open: () => ui.openApprovalDetail(a.instanceId, a.approval.id),
150
156
  }
151
157
  return null
@@ -155,7 +161,7 @@ const attention = computed<{
155
161
  * decision/approval reason, otherwise the generic status label. */
156
162
  const statusText = computed(() =>
157
163
  runFailed.value
158
- ? 'Failed'
164
+ ? t('board.task.failed')
159
165
  : (reviewStageLabel.value ?? attention.value?.label ?? statusMeta.value?.label ?? ''),
160
166
  )
161
167
 
@@ -191,7 +197,11 @@ function selectTask() {
191
197
  v-if="schedule"
192
198
  name="i-lucide-repeat"
193
199
  class="h-3 w-3 shrink-0 text-indigo-400"
194
- :title="schedule.enabled ? 'Recurring pipeline' : 'Recurring pipeline (paused)'"
200
+ :title="
201
+ schedule.enabled
202
+ ? t('board.task.recurringPipeline')
203
+ : t('board.task.recurringPipelinePaused')
204
+ "
195
205
  />
196
206
  <span
197
207
  class="ml-auto truncate text-[9px] uppercase tracking-wide"
@@ -211,7 +221,7 @@ function selectTask() {
211
221
  <button
212
222
  type="button"
213
223
  class="nodrag shrink-0 cursor-crosshair touch-none rounded-full p-0.5 text-slate-500 hover:bg-slate-800 hover:text-amber-400 pointer-coarse:p-2.5"
214
- title="Drag onto another task to make it depend on this one"
224
+ :title="t('board.task.dragToConnect')"
215
225
  @pointerdown.stop="startConnect(task.id, $event)"
216
226
  @click.stop
217
227
  >
@@ -302,12 +312,20 @@ function selectTask() {
302
312
  :disabled="!runnable || starting"
303
313
  :title="
304
314
  runnable
305
- ? `Start ${defaultPipeline?.name ?? 'pipeline'}`
306
- : `Waiting on: ${unmet.map((d) => d.title).join(', ')}`
315
+ ? t('board.task.startPipeline', {
316
+ name: defaultPipeline?.name ?? t('board.task.pipelineFallback'),
317
+ })
318
+ : t('board.task.waitingOn', { deps: unmet.map((d) => d.title).join(', ') })
307
319
  "
308
320
  @click.stop="run"
309
321
  >
310
- {{ starting ? 'Starting…' : runnable ? 'Start' : 'Blocked' }}
322
+ {{
323
+ starting
324
+ ? t('board.task.starting')
325
+ : runnable
326
+ ? t('board.task.start')
327
+ : t('board.task.blocked')
328
+ }}
311
329
  </UButton>
312
330
  <span
313
331
  v-if="runnable && defaultPipeline"
@@ -328,7 +346,7 @@ function selectTask() {
328
346
  variant="soft"
329
347
  size="xs"
330
348
  icon="i-lucide-git-pull-request"
331
- :title="`Open ${prLabel} on GitHub`"
349
+ :title="t('board.task.openPrOnGithub', { pr: prLabel })"
332
350
  @click.stop
333
351
  >
334
352
  {{ prLabel }}
@@ -340,7 +358,7 @@ function selectTask() {
340
358
  icon="i-lucide-scan-eye"
341
359
  @click.stop="review"
342
360
  >
343
- Review
361
+ {{ t('board.task.review') }}
344
362
  </UButton>
345
363
  <UButton
346
364
  color="success"
@@ -349,7 +367,7 @@ function selectTask() {
349
367
  icon="i-lucide-git-merge"
350
368
  @click.stop="merge"
351
369
  >
352
- Merge
370
+ {{ t('board.task.merge') }}
353
371
  </UButton>
354
372
  </template>
355
373
 
@@ -357,7 +375,7 @@ function selectTask() {
357
375
  v-else-if="task.status === 'done'"
358
376
  class="inline-flex items-center gap-1 text-[9px] text-emerald-400"
359
377
  >
360
- <UIcon name="i-lucide-check-check" class="h-3 w-3" /> implemented
378
+ <UIcon name="i-lucide-check-check" class="h-3 w-3" /> {{ t('board.task.implemented') }}
361
379
  </span>
362
380
  </div>
363
381
 
@@ -368,7 +386,7 @@ function selectTask() {
368
386
  >
369
387
  <span
370
388
  class="inline-flex items-center gap-1 rounded bg-violet-500/15 px-1.5 py-0.5 text-[9px] text-violet-200"
371
- :title="`Module: ${task.moduleName}`"
389
+ :title="t('board.task.module', { name: task.moduleName })"
372
390
  >
373
391
  <UIcon :name="MODULE_META.icon" class="h-3 w-3" :style="{ color: MODULE_META.color }" />
374
392
  {{ task.moduleName }}
@@ -21,6 +21,7 @@ const execution = useExecutionStore()
21
21
  const ui = useUiStore()
22
22
  const expansion = useTaskExpansionStore()
23
23
  const reviews = useReviewStage()
24
+ const { t } = useI18n()
24
25
  const { lod } = useSemanticZoom()
25
26
 
26
27
  const instance = computed(() => execution.getByBlock(props.taskId))
@@ -75,12 +76,12 @@ const ITEM_ICON: Record<string, string> = {
75
76
  <div v-if="showSteps" class="mt-2 space-y-1 border-t border-slate-800 pt-2">
76
77
  <div class="flex items-center gap-1 text-[9px] uppercase tracking-wide text-slate-500">
77
78
  <UIcon name="i-lucide-workflow" class="h-2.5 w-2.5" />
78
- Build steps
79
+ {{ t('board.task.buildSteps') }}
79
80
  </div>
80
81
  <div v-for="(s, i) in steps" :key="i" class="rounded bg-slate-900/60 px-1.5 py-1">
81
82
  <div
82
83
  class="flex cursor-pointer items-center gap-1"
83
- :title="`${agentKindMeta(s.agentKind).label} — ${agentKindMeta(s.agentKind).description}\nClick to view step details & output`"
84
+ :title="`${agentKindMeta(s.agentKind).label} — ${agentKindMeta(s.agentKind).description}\n${t('board.task.clickToViewStep')}`"
84
85
  @click.stop="openStep(i)"
85
86
  >
86
87
  <UIcon
@@ -131,7 +132,7 @@ const ITEM_ICON: Record<string, string> = {
131
132
  @click.stop="ui.openApprovalDetail(instance.id, s.approval.id)"
132
133
  >
133
134
  <UIcon name="i-lucide-shield-check" class="h-2.5 w-2.5" />
134
- Review &amp; approve
135
+ {{ t('board.task.reviewAndApprove') }}
135
136
  </button>
136
137
 
137
138
  <!-- per-step subtask progress bar -->
@@ -8,15 +8,20 @@ const execution = useExecutionStore()
8
8
  const workspace = useWorkspaceStore()
9
9
  const services = useServicesStore()
10
10
  const toast = useToast()
11
+ const { t, n } = useI18n()
11
12
  const { fitView, zoomIn, zoomOut } = useBoardFlow()
12
13
 
13
14
  async function mountService(serviceId: string, title: string) {
14
15
  try {
15
16
  await services.mount(serviceId)
16
- toast.add({ title: `Added ${title}`, icon: 'i-lucide-box', color: 'success' })
17
+ toast.add({
18
+ title: t('board.toolbar.serviceAdded', { title }),
19
+ icon: 'i-lucide-box',
20
+ color: 'success',
21
+ })
17
22
  } catch (e) {
18
23
  toast.add({
19
- title: 'Could not add service',
24
+ title: t('board.toolbar.serviceAddFailed'),
20
25
  description: e instanceof Error ? e.message : String(e),
21
26
  color: 'error',
22
27
  })
@@ -38,16 +43,16 @@ const mountableItems = computed(() =>
38
43
  )
39
44
 
40
45
  const zoomPct = computed(() => Math.round(ui.zoom * 100))
41
- const lodLabel = computed(
42
- () =>
43
- ({
44
- far: 'Overview',
45
- mid: 'Summary',
46
- close: 'Detail',
47
- steps: 'Build steps',
48
- subtasks: 'Subtasks',
49
- })[ui.lod],
50
- )
46
+ // Exhaustive (tier-2) map from level-of-detail → its label key, so adding an LOD
47
+ // without a label fails the typecheck rather than leaking a raw key.
48
+ const LOD_LABEL_KEYS = {
49
+ far: 'board.toolbar.lod.far',
50
+ mid: 'board.toolbar.lod.mid',
51
+ close: 'board.toolbar.lod.close',
52
+ steps: 'board.toolbar.lod.steps',
53
+ subtasks: 'board.toolbar.lod.subtasks',
54
+ } as const
55
+ const lodLabel = computed(() => t(LOD_LABEL_KEYS[ui.lod]))
51
56
 
52
57
  // Live spend indicator: shown once any tokens have been metered this period.
53
58
  const spend = computed(() => workspace.spend)
@@ -55,11 +60,11 @@ const showSpend = computed(() => !!spend.value && spend.value.costSpent > 0)
55
60
  const spendLabel = computed(() => {
56
61
  const s = spend.value
57
62
  if (!s) return ''
58
- const fmt = (n: number) => {
63
+ const fmt = (value: number) => {
59
64
  try {
60
- return new Intl.NumberFormat(undefined, { style: 'currency', currency: s.currency }).format(n)
65
+ return n(value, { key: 'currency', currency: s.currency })
61
66
  } catch {
62
- return `${n.toFixed(2)} ${s.currency}`
67
+ return `${value.toFixed(2)} ${s.currency}`
63
68
  }
64
69
  }
65
70
  return `${fmt(s.costSpent)} / ${fmt(s.costLimit)}`
@@ -70,7 +75,7 @@ const decisionItems = computed(() =>
70
75
  execution.openDecisions.map((d) => {
71
76
  const b = board.getBlock(d.blockId)
72
77
  return {
73
- label: b?.title ?? 'Block',
78
+ label: b?.title ?? t('common.block'),
74
79
  description: d.decision.question,
75
80
  icon: 'i-lucide-circle-help',
76
81
  onSelect: () => ui.openDecision(d.instanceId, d.decision.id),
@@ -127,7 +132,7 @@ const decisionItems = computed(() =>
127
132
  >
128
133
  {{ execution.pendingDecisionCount
129
134
  }}<span class="hidden sm:inline"
130
- >&nbsp;{{ $t('board.toolbar.decisionWord', execution.pendingDecisionCount) }}</span
135
+ >&nbsp;{{ t('board.toolbar.decisionWord', execution.pendingDecisionCount) }}</span
131
136
  >
132
137
  </UButton>
133
138
  </UDropdownMenu>
@@ -135,7 +140,7 @@ const decisionItems = computed(() =>
135
140
  <!-- in-org sharing: add an existing org service to this board -->
136
141
  <UDropdownMenu v-if="mountableItems.length" :items="mountableItems">
137
142
  <UButton color="neutral" variant="ghost" size="sm" icon="i-lucide-plus-circle">
138
- <span class="hidden sm:inline">{{ $t('board.toolbar.addService') }}</span>
143
+ <span class="hidden sm:inline">{{ t('board.toolbar.addService') }}</span>
139
144
  </UButton>
140
145
  </UDropdownMenu>
141
146
 
@@ -149,7 +154,9 @@ const decisionItems = computed(() =>
149
154
  variant="soft"
150
155
  size="sm"
151
156
  icon="i-lucide-wallet"
152
- :title="spend?.exceeded ? 'Spend limit reached — runs paused' : 'Token spend this month'"
157
+ :title="
158
+ spend?.exceeded ? t('board.toolbar.spendLimitReached') : t('board.toolbar.spendTitle')
159
+ "
153
160
  >
154
161
  <span class="hidden sm:inline">{{ spendLabel }}</span>
155
162
  </UButton>
@@ -12,6 +12,10 @@
12
12
  "save": "Save",
13
13
  "cancel": "Cancel",
14
14
  "retry": "Retry",
15
+ "block": "Block",
16
+ "@block": {
17
+ "description": "Generic fallback NOUN for a board item whose title is unknown (a service / module / task node). Not the verb 'to block'."
18
+ },
15
19
  "actionFailed": "Action failed",
16
20
  "close": "Close",
17
21
  "@close": {
@@ -50,7 +54,207 @@
50
54
  "decisionWord": "decision | decisions",
51
55
  "@decisionWord": {
52
56
  "description": "Count-based plural noun rendered AFTER a number, e.g. '3 decisions' (pending human decisions in the run queue). Resolved via t(key, count); count is always 1 or more. Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - and rely on the custom pluralRules wired in i18n.config.ts)."
57
+ },
58
+ "lod": {
59
+ "far": "Overview",
60
+ "mid": "Summary",
61
+ "close": "Detail",
62
+ "steps": "Build steps",
63
+ "subtasks": "Subtasks"
64
+ },
65
+ "spendTitle": "Token spend this month",
66
+ "spendLimitReached": "Spend limit reached — runs paused",
67
+ "serviceAdded": "Added {title}",
68
+ "serviceAddFailed": "Could not add service"
69
+ },
70
+ "canvas": {
71
+ "emptyTitle": "Your board is empty",
72
+ "emptyBody": "Add a service to get started: bootstrap a fresh repo or pull in one you already have.",
73
+ "addBlockFailedTitle": "Could not add block",
74
+ "addBlockFailedBody": "The backend rejected the request.",
75
+ "dropOntoTaskTitle": "Drop onto a task",
76
+ "dropOntoTaskBody": "Pipelines run against tasks, not services.",
77
+ "taskBlockedTitle": "Task is blocked",
78
+ "taskBlockedBody": "Its dependencies haven't merged yet."
79
+ },
80
+ "addTask": {
81
+ "title": "Add a task",
82
+ "newTaskIn": "New task in {container}",
83
+ "typeLabel": "Type",
84
+ "types": {
85
+ "feature": "Feature",
86
+ "bug": "Bug",
87
+ "document": "Document",
88
+ "spike": "Spike",
89
+ "recurring": "Recurring"
90
+ },
91
+ "recurringWithFrame": "A recurring task runs a pipeline on a cadence. Continue to set the schedule + prompt.",
92
+ "recurringNoFrame": "A recurring task must live on a service. Add it from a service frame (or a module inside one).",
93
+ "titleField": "Title",
94
+ "titlePlaceholder": "What needs to be done?",
95
+ "issueIncluded": "{title} (from issue, included)",
96
+ "loadingIssue": "Loading the linked issue's description…",
97
+ "additionalNotes": "Additional notes",
98
+ "description": "Description",
99
+ "notesPlaceholder": "Add anything else the agent should know — appended to the issue description above…",
100
+ "descriptionPlaceholder": "Describe the work — context, acceptance criteria, anything the agent should know…",
101
+ "technical": "Technical task",
102
+ "technicalHint": "A refactor / non-functional / internal change. The implementer treats the task definition as primary and the spec as a regression reference; leave off to let the spec phase decide.",
103
+ "severity": "Severity",
104
+ "stepsToReproduce": "Steps to reproduce",
105
+ "stepsToReproducePlaceholder": "Observed vs expected, and how to reproduce…",
106
+ "timebox": "Time-box (hours)",
107
+ "timeboxPlaceholder": "e.g. 8",
108
+ "documentKind": "Document kind",
109
+ "audience": "Audience",
110
+ "audiencePlaceholder": "e.g. platform engineers",
111
+ "targetPath": "Target path",
112
+ "targetPathPlaceholder": "e.g. docs/rfcs/0001-foo.md",
113
+ "outlineHints": "Outline hints",
114
+ "outlineHintsPlaceholder": "Sections or points the document should cover",
115
+ "optional": "optional",
116
+ "pipeline": "Pipeline",
117
+ "chooseAtRunTime": "Choose at run time",
118
+ "mergePolicy": "Merge policy",
119
+ "workspaceDefault": "Workspace default",
120
+ "defaultPreset": "Default ({name}) — {thresholds}",
121
+ "defaultModelPreset": "Default ({name})",
122
+ "modelPreset": "Model preset",
123
+ "agentConfiguration": "Agent configuration",
124
+ "contextDocuments": "Context documents",
125
+ "contextIssues": "Context issues",
126
+ "attach": "Attach",
127
+ "done": "Done",
128
+ "attachDocDisabledConnect": "Connect a document source first (Integrations)",
129
+ "attachDocDisabledEnable": "Enable the documents integration first",
130
+ "attachIssueDisabledConnect": "Connect an issue tracker first (Integrations)",
131
+ "attachIssueDisabledEnable": "Enable the issue-tracker integration first",
132
+ "importsOnAdd": "imports on add",
133
+ "noDocsHint": "Attach a requirement, RFC or PRD so agents see it while implementing this task.",
134
+ "noIssuesHint": "Attach a tracker issue so agents see its description and comments while implementing this task.",
135
+ "plannedHint": "The task is added in a planned state. It won't run until you start a pipeline on it — you can keep editing it until then.",
136
+ "continue": "Continue",
137
+ "submit": "Add task",
138
+ "addFailedTitle": "Could not add task",
139
+ "linkFailed": "Task added, but {count} attachment could not be linked | Task added, but {count} attachments could not be linked",
140
+ "@linkFailed": {
141
+ "description": "Count-based: how many context attachments (docs/issues) failed to link after the task was created (count is always >= 1). Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - via the custom pluralRules in i18n.config.ts)."
53
142
  }
143
+ },
144
+ "recurring": {
145
+ "title": "Add a recurring pipeline",
146
+ "on": "Recurring pipeline on {frame}",
147
+ "name": "Name",
148
+ "namePlaceholder": "e.g. Weekly dependency updates",
149
+ "pipeline": "Pipeline",
150
+ "pickPipeline": "Pick a pipeline",
151
+ "prompt": "Prompt",
152
+ "promptPlaceholder": "What should each run do? Describe the work — the same prompt a normal task carries. Leave blank to use the pipeline's default.",
153
+ "issueTracker": "Issue tracker",
154
+ "issueTrackerHint": "The tech-debt pipeline files a ticket from its analysis before implementing. Choose where (saved for the whole workspace).",
155
+ "githubIssues": "GitHub Issues",
156
+ "jira": "Jira",
157
+ "linear": "Linear",
158
+ "jiraProjectKey": "Jira project key",
159
+ "jiraProjectKeyPlaceholder": "e.g. ENG",
160
+ "linearTeamId": "Linear team id",
161
+ "footerHint": "A single recurring task is added inside the service; each run replaces the last. Its run history is visible in the inspector.",
162
+ "submit": "Add recurring pipeline",
163
+ "addFailedTitle": "Could not add recurring pipeline"
164
+ },
165
+ "failure": {
166
+ "containerFailedToStart": "Container failed to start",
167
+ "bootstrapFailed": "Bootstrap failed",
168
+ "runFailed": "Run failed",
169
+ "retryBootstrap": "Retry bootstrap",
170
+ "retryRun": "Retry run",
171
+ "showDetail": "Show detail",
172
+ "retrying": "Retrying…"
173
+ },
174
+ "stop": {
175
+ "label": "Stop",
176
+ "bootstrapStopped": "Bootstrap stopped",
177
+ "runStopped": "Run stopped",
178
+ "stoppedDescription": "The container was killed and the run was cancelled.",
179
+ "stopFailed": "Stop failed"
180
+ },
181
+ "frame": {
182
+ "status": {
183
+ "planned": "No tasks",
184
+ "ready": "Live",
185
+ "in_progress": "Active",
186
+ "blocked": "Needs attention",
187
+ "pr_ready": "Active",
188
+ "done": "Live"
189
+ },
190
+ "shared": "Shared",
191
+ "sharedTitle": "Shared across workspaces in this org",
192
+ "bootstrapping": "Bootstrapping…",
193
+ "bootstrappingRepository": "Bootstrapping repository…",
194
+ "bootstrapStepsCount": "{completed}/{total} steps",
195
+ "runFailed": "Run failed",
196
+ "mergedOfTotal": "{merged}/{total} merged",
197
+ "noTasksYet": "No tasks yet",
198
+ "prCount": "{count} PR",
199
+ "implemented": "{merged}/{total} implemented",
200
+ "prReadyCount": "{count} PR ready",
201
+ "taskCount": "{count} task | {count} tasks",
202
+ "@taskCount": {
203
+ "description": "Count-based task tally rendered as e.g. '3 tasks' (count is always >= 0). Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - via the custom pluralRules in i18n.config.ts)."
204
+ },
205
+ "moduleCount": "{count} module | {count} modules",
206
+ "@moduleCount": {
207
+ "description": "Count-based module tally rendered as e.g. '2 modules' (count is always >= 1). Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - via the custom pluralRules in i18n.config.ts)."
208
+ },
209
+ "addTaskTitle": "Add task",
210
+ "createTaskFromIssueTitle": "Create task from issue",
211
+ "addRecurringTitle": "Add recurring pipeline",
212
+ "collapseTitle": "Collapse",
213
+ "dragService": "Drag service",
214
+ "dragTask": "Drag task",
215
+ "dragToResize": "Drag to resize",
216
+ "addFirstTask": "Add the first task"
217
+ },
218
+ "decisionBadge": {
219
+ "decisionNeeded": "Decision needed",
220
+ "approvalNeeded": "Approval needed"
221
+ },
222
+ "task": {
223
+ "recurringPipeline": "Recurring pipeline",
224
+ "recurringPipelinePaused": "Recurring pipeline (paused)",
225
+ "dragToConnect": "Drag onto another task to make it depend on this one",
226
+ "failed": "Failed",
227
+ "decisionNeeded": "Decision needed",
228
+ "approvalNeeded": "Approval needed",
229
+ "resolve": "Resolve",
230
+ "approve": "Approve",
231
+ "incorporatingAnswers": "Incorporating answers…",
232
+ "reReviewing": "Re-reviewing…",
233
+ "recommending": "Recommending…",
234
+ "starting": "Starting…",
235
+ "start": "Start",
236
+ "blocked": "Blocked",
237
+ "startPipeline": "Start {name}",
238
+ "pipelineFallback": "pipeline",
239
+ "waitingOn": "Waiting on: {deps}",
240
+ "blockedByDependenciesTitle": "Blocked by dependencies",
241
+ "noPipelineTitle": "No pipeline defined",
242
+ "noPipelineBody": "Create one in the builder first.",
243
+ "pr": "PR",
244
+ "prNumber": "PR #{number}",
245
+ "openPrOnGithub": "Open {pr} on GitHub",
246
+ "review": "Review",
247
+ "merge": "Merge",
248
+ "implemented": "implemented",
249
+ "module": "Module: {name}",
250
+ "buildSteps": "Build steps",
251
+ "reviewAndApprove": "Review & approve",
252
+ "clickToViewStep": "Click to view step details & output"
253
+ },
254
+ "epic": {
255
+ "label": "Epic",
256
+ "activeCount": "{count} active",
257
+ "noTasksYet": "No tasks yet"
54
258
  }
55
259
  },
56
260
  "errors": {