@cat-factory/app 0.82.1 → 0.83.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.
@@ -36,13 +36,55 @@ export const useBoardStore = defineStore('board', () => {
36
36
  // global i18n instance (the same handle `plugins/locale.client.ts` uses) rather than
37
37
  // `useI18n()`, which requires an active component instance.
38
38
  const nuxtApp = useNuxtApp()
39
- const tr = (key: string): string => (nuxtApp.$i18n as { t: (k: string) => string }).t(key)
39
+ const tr = (key: string, params?: Record<string, unknown>): string =>
40
+ (nuxtApp.$i18n as { t: (k: string, p?: Record<string, unknown>) => string }).t(
41
+ key,
42
+ params ?? {},
43
+ )
40
44
  const blocks = ref<Block[]>([])
41
45
 
42
46
  // Pure derivations (hierarchy, status/progress, sizing) live in the composable.
43
47
  const queries = useBlockQueries(blocks)
44
48
  const { getBlock } = queries
45
49
 
50
+ /**
51
+ * How long a deleted block stays undoable. The backend delete is DEFERRED for this
52
+ * window (a real "undo", not a client illusion) — the block is hidden immediately but
53
+ * only actually deleted once the window elapses, so undo just cancels the pending call.
54
+ */
55
+ const UNDO_WINDOW_MS = 6000
56
+ /**
57
+ * Blocks hidden by an optimistic delete whose backend call hasn't fired yet, keyed by
58
+ * the deleted root's id. Their subtree stays filtered out of every incoming server
59
+ * snapshot (`hydrate`) and single-block live event (`upsert`) for the undo window, so a
60
+ * coarse refresh or a stray event can't resurrect a block the user just deleted.
61
+ */
62
+ const pendingRemovals = new Map<
63
+ string,
64
+ { snap: RemovalSnapshot; timer: ReturnType<typeof setTimeout>; wsId: string }
65
+ >()
66
+ // Flat set of every id in a pending removal (root + descendants), for O(1) checks in the
67
+ // hot upsert path. Kept in lockstep with `pendingRemovals`.
68
+ const pendingDoomed = new Set<string>()
69
+
70
+ /**
71
+ * Drop any pending-removal subtree from a reconciled block list and prune survivors'
72
+ * edges to it — the same detach the backend will perform once the deferred delete fires.
73
+ * Applied to every hydrate so the undo window survives a full refresh.
74
+ */
75
+ function applyPendingRemovals(list: Block[]): Block[] {
76
+ if (pendingDoomed.size === 0) return list
77
+ const survivors = list.filter((b) => !pendingDoomed.has(b.id))
78
+ for (const b of survivors) {
79
+ if (b.dependsOn.some((d) => pendingDoomed.has(d))) {
80
+ b.dependsOn = b.dependsOn.filter((d) => !pendingDoomed.has(d))
81
+ }
82
+ if (b.epicId != null && pendingDoomed.has(b.epicId)) b.epicId = null
83
+ if (b.initiativeId != null && pendingDoomed.has(b.initiativeId)) b.initiativeId = null
84
+ }
85
+ return survivors
86
+ }
87
+
46
88
  /**
47
89
  * Reconcile the cached blocks against a server snapshot, reusing the existing
48
90
  * object for any block whose content is unchanged. The server stays authoritative
@@ -67,14 +109,18 @@ export const useBoardStore = defineStore('board', () => {
67
109
  }
68
110
  function hydrate(next: Block[]) {
69
111
  const prev = new Map(blocks.value.map((b) => [b.id, b]))
70
- blocks.value = next.map((n) => {
112
+ const reconciled = next.map((n) => {
71
113
  const existing = prev.get(n.id)
72
114
  return existing && jsonFor(existing) === jsonFor(n) ? existing : n
73
115
  })
116
+ // Keep blocks the user just deleted hidden while their delete is still pending.
117
+ blocks.value = applyPendingRemovals(reconciled)
74
118
  }
75
119
 
76
120
  /** Insert or replace a block returned by the backend. */
77
121
  function upsert(block: Block) {
122
+ // A live event for a block awaiting its deferred delete must not resurrect it.
123
+ if (pendingDoomed.has(block.id)) return
78
124
  const i = blocks.value.findIndex((b) => b.id === block.id)
79
125
  if (i >= 0) blocks.value[i] = block
80
126
  else blocks.value.push(block)
@@ -199,11 +245,16 @@ export const useBoardStore = defineStore('board', () => {
199
245
  return block
200
246
  }
201
247
 
202
- /** Move a block into a new container at a new local position. */
248
+ /**
249
+ * Move a block into a new container at a new local position. Drag-reparent commits
250
+ * silently on a small overshoot, so a successful move (into a *different* container,
251
+ * not an undo of one) offers a one-click undo back to its previous home.
252
+ */
203
253
  async function reparentBlock(
204
254
  id: string,
205
255
  newParentId: string,
206
256
  position: { x: number; y: number },
257
+ opts: { undoable?: boolean } = { undoable: true },
207
258
  ) {
208
259
  const b = getBlock(id)
209
260
  const parent = getBlock(newParentId)
@@ -217,6 +268,7 @@ export const useBoardStore = defineStore('board', () => {
217
268
  // block in the wrong container (a structural lie that survives until re-hydrate).
218
269
  const prevParentId = b.parentId
219
270
  const prevPosition = b.position
271
+ const name = b.title
220
272
  b.parentId = newParentId
221
273
  b.position = position
222
274
  try {
@@ -226,6 +278,24 @@ export const useBoardStore = defineStore('board', () => {
226
278
  position,
227
279
  }),
228
280
  )
281
+ // Offer an undo back to the previous container (a drag overshoot is easy). The undo
282
+ // move is itself non-undoable so the toast doesn't ping-pong.
283
+ if (opts.undoable && prevParentId) {
284
+ toast.add({
285
+ title: tr('board.toast.moved', { name }),
286
+ icon: 'i-lucide-move',
287
+ color: 'neutral',
288
+ duration: UNDO_WINDOW_MS,
289
+ actions: [
290
+ {
291
+ label: tr('common.undo'),
292
+ icon: 'i-lucide-undo-2',
293
+ onClick: () =>
294
+ void reparentBlock(id, prevParentId, prevPosition, { undoable: false }),
295
+ },
296
+ ],
297
+ })
298
+ }
229
299
  } catch (e) {
230
300
  b.parentId = prevParentId
231
301
  b.position = prevPosition
@@ -291,35 +361,99 @@ export const useBoardStore = defineStore('board', () => {
291
361
  /** Re-insert a detached subtree and restore its broken edges (delete rollback). */
292
362
  function reattach(snap: RemovalSnapshot) {
293
363
  for (const b of snap.removed) if (!getBlock(b.id)) blocks.value.push(b)
364
+ const restored = new Set(snap.removed.map((b) => b.id))
294
365
  for (const e of snap.edges) {
295
366
  const b = getBlock(e.id)
296
- if (b) {
297
- b.dependsOn = e.dependsOn
298
- b.epicId = e.epicId
367
+ if (!b) continue
368
+ // Re-establish only the links that pointed at a now-restored block, merged with
369
+ // whatever the survivor gained meanwhile — so a delayed undo (the delete is deferred by
370
+ // a window during which a live event may add edges) doesn't clobber a newer dependency /
371
+ // epic / initiative link with the stale detach-time snapshot.
372
+ const readd = e.dependsOn.filter((d) => restored.has(d) && !b.dependsOn.includes(d))
373
+ if (readd.length) b.dependsOn = [...b.dependsOn, ...readd]
374
+ if (b.epicId == null && e.epicId != null && restored.has(e.epicId)) b.epicId = e.epicId
375
+ if (b.initiativeId == null && e.initiativeId != null && restored.has(e.initiativeId)) {
299
376
  b.initiativeId = e.initiativeId
300
377
  }
301
378
  }
302
379
  }
303
380
 
304
381
  /**
305
- * Delete a block. The subtree is hidden IMMEDIATELY (optimistic) so the board
306
- * feels instant; if the backend rejects the delete we put it back and surface a
307
- * toast rather than silently leaving a ghost.
382
+ * Delete a block. The subtree is hidden IMMEDIATELY (optimistic) so the board feels
383
+ * instant, and the real backend delete is DEFERRED by {@link UNDO_WINDOW_MS} so the
384
+ * accompanying "Deleted — Undo" toast can cancel it in place (a genuine undo, since
385
+ * nothing was destroyed server-side yet). The subtree stays filtered out of any
386
+ * hydrate/upsert in the meantime (see {@link applyPendingRemovals}). If the deferred
387
+ * call ultimately fails we put the subtree back and surface an error toast.
308
388
  */
309
- async function removeBlock(id: string) {
389
+ function removeBlock(
390
+ id: string,
391
+ opts: { onCommit?: (wsId: string) => void | Promise<void> } = {},
392
+ ) {
393
+ const block = getBlock(id)
310
394
  const snap = detach(id)
311
- if (!snap) return
312
- try {
313
- await api.removeBlock(useWorkspaceStore().requireId(), id)
314
- } catch (e) {
315
- reattach(snap)
316
- toast.add({
317
- title: tr('board.toast.deleteFailed'),
318
- description: e instanceof Error ? e.message : String(e),
319
- icon: 'i-lucide-triangle-alert',
320
- color: 'error',
321
- })
395
+ if (!block || !snap) return
396
+ // Capture the workspace now: the deferred delete must target the workspace the block
397
+ // was deleted from even if the user has since switched.
398
+ const wsId = useWorkspaceStore().requireId()
399
+ for (const b of snap.removed) pendingDoomed.add(b.id)
400
+
401
+ const finalize = async () => {
402
+ const pending = pendingRemovals.get(id)
403
+ if (!pending) return
404
+ pendingRemovals.delete(id)
405
+ try {
406
+ // Any irreversible side effect the delete implies (e.g. cancelling the block's run)
407
+ // is deferred to here so it fires only once the delete truly commits — undo within
408
+ // the window then leaves the run untouched instead of restoring an already-cancelled one.
409
+ await opts.onCommit?.(pending.wsId)
410
+ await api.removeBlock(pending.wsId, id)
411
+ // Stop filtering the subtree only after the server has actually dropped it, so a
412
+ // snapshot that raced the in-flight delete can't briefly resurrect it.
413
+ for (const b of pending.snap.removed) pendingDoomed.delete(b.id)
414
+ } catch (e) {
415
+ for (const b of pending.snap.removed) pendingDoomed.delete(b.id)
416
+ // Only restore into the board the block was deleted from; a mid-window workspace
417
+ // switch must not inject the old subtree onto the workspace now on screen (it
418
+ // re-hydrates from the server there on the next refresh anyway).
419
+ if (useWorkspaceStore().workspaceId === pending.wsId) reattach(pending.snap)
420
+ toast.add({
421
+ title: tr('board.toast.deleteFailed'),
422
+ description: e instanceof Error ? e.message : String(e),
423
+ icon: 'i-lucide-triangle-alert',
424
+ color: 'error',
425
+ })
426
+ }
322
427
  }
428
+ const timer = setTimeout(() => void finalize(), UNDO_WINDOW_MS)
429
+ pendingRemovals.set(id, { snap, timer, wsId })
430
+
431
+ toast.add({
432
+ title: tr('board.toast.deleted', { name: block.title }),
433
+ icon: 'i-lucide-trash-2',
434
+ color: 'neutral',
435
+ duration: UNDO_WINDOW_MS,
436
+ actions: [
437
+ {
438
+ label: tr('common.undo'),
439
+ icon: 'i-lucide-undo-2',
440
+ onClick: () => undoRemove(id),
441
+ },
442
+ ],
443
+ })
444
+ }
445
+
446
+ /** Cancel a still-pending delete and restore the hidden subtree. */
447
+ function undoRemove(id: string) {
448
+ const pending = pendingRemovals.get(id)
449
+ if (!pending) return
450
+ clearTimeout(pending.timer)
451
+ pendingRemovals.delete(id)
452
+ for (const b of pending.snap.removed) pendingDoomed.delete(b.id)
453
+ // Don't resurrect the subtree into a different workspace if the user navigated away
454
+ // mid-window; the delete was already cancelled, which is the safe outcome there.
455
+ if (useWorkspaceStore().workspaceId !== pending.wsId) return
456
+ reattach(pending.snap)
323
457
  }
324
458
 
325
459
  /**
@@ -357,7 +491,7 @@ export const useBoardStore = defineStore('board', () => {
357
491
  // spot the server never stored (a lie that survives until the next re-hydrate).
358
492
  b.position = prevPosition
359
493
  toast.add({
360
- title: 'Could not move',
494
+ title: tr('board.toast.moveFailed'),
361
495
  description: e instanceof Error ? e.message : String(e),
362
496
  icon: 'i-lucide-triangle-alert',
363
497
  color: 'error',
@@ -304,10 +304,14 @@ export const useExecutionStore = defineStore('execution', () => {
304
304
  }
305
305
  }
306
306
 
307
- /** Cancel the execution running against a block and reset it to planned. */
308
- async function cancel(blockId: string) {
307
+ /**
308
+ * Cancel the execution running against a block and reset it to planned. `workspaceId`
309
+ * defaults to the current workspace but can be pinned by callers that cancel a run for a
310
+ * board the user may have since navigated away from (e.g. a deferred delete's commit).
311
+ */
312
+ async function cancel(blockId: string, workspaceId?: string) {
309
313
  const ws = useWorkspaceStore()
310
- await api.cancelExecution(ws.requireId(), blockId)
314
+ await api.cancelExecution(workspaceId ?? ws.requireId(), blockId)
311
315
  instances.value = instances.value.filter((e) => e.blockId !== blockId)
312
316
  await ws.refresh()
313
317
  }
@@ -90,9 +90,66 @@ export const useInitiativesStore = defineStore('initiatives', () => {
90
90
  }
91
91
  }
92
92
 
93
+ /** True while a planning-window action (continue/proceed) is resuming the run. */
94
+ const resuming = ref(false)
95
+
96
+ /** Record the human's answer to one pending interview question (no run resume). */
97
+ async function answerQuestion(blockId: string, questionId: string, answer: string) {
98
+ if (!workspace.workspaceId) throw new Error('No active workspace')
99
+ const updated = await api.answerInitiativeQuestion(
100
+ workspace.workspaceId,
101
+ blockId,
102
+ questionId,
103
+ answer,
104
+ )
105
+ upsert(updated)
106
+ return updated
107
+ }
108
+
109
+ /** Submit the answers and resume the interview (the interviewer re-runs, may ask more). */
110
+ async function continuePlanning(blockId: string) {
111
+ if (!workspace.workspaceId) throw new Error('No active workspace')
112
+ resuming.value = true
113
+ try {
114
+ const updated = await api.continueInitiativePlanning(workspace.workspaceId, blockId)
115
+ upsert(updated)
116
+ return updated
117
+ } finally {
118
+ resuming.value = false
119
+ }
120
+ }
121
+
122
+ /** Skip remaining questions: the interviewer converges and the run advances. */
123
+ async function proceedPlanning(blockId: string) {
124
+ if (!workspace.workspaceId) throw new Error('No active workspace')
125
+ resuming.value = true
126
+ try {
127
+ const updated = await api.proceedInitiativePlanning(workspace.workspaceId, blockId)
128
+ upsert(updated)
129
+ return updated
130
+ } finally {
131
+ resuming.value = false
132
+ }
133
+ }
134
+
93
135
  function reset() {
94
136
  byBlock.value = {}
95
137
  }
96
138
 
97
- return { available, byBlock, all, creating, forBlock, hydrate, upsert, create, load, reset }
139
+ return {
140
+ available,
141
+ byBlock,
142
+ all,
143
+ creating,
144
+ resuming,
145
+ forBlock,
146
+ hydrate,
147
+ upsert,
148
+ create,
149
+ load,
150
+ answerQuestion,
151
+ continuePlanning,
152
+ proceedPlanning,
153
+ reset,
154
+ }
98
155
  })
package/app/stores/ui.ts CHANGED
@@ -728,6 +728,11 @@ export const useUiStore = defineStore('ui', () => {
728
728
  function openInitiativeTracker(blockId: string) {
729
729
  resultView.value = { view: 'initiative-tracker', blockId, instanceId: null, stepIndex: null }
730
730
  }
731
+ // Open the interactive-planning Q&A window for an initiative block (inspector / card,
732
+ // when the interviewer has parked the planning run with pending questions).
733
+ function openInitiativePlanning(blockId: string) {
734
+ resultView.value = { view: 'initiative-planning', blockId, instanceId: null, stepIndex: null }
735
+ }
731
736
  // Open the Follow-up companion window for a run's Coder step (the blinking chip + the
732
737
  // `followup_pending` notification). Resolves the Coder step index from the run when not
733
738
  // given, so callers that only know the run can still open it.
@@ -939,6 +944,7 @@ export const useUiStore = defineStore('ui', () => {
939
944
  openBrainstorm,
940
945
  openServiceSpec,
941
946
  openInitiativeTracker,
947
+ openInitiativePlanning,
942
948
  openFollowUps,
943
949
  closeRequirementReview,
944
950
  openStepDetail,
@@ -347,6 +347,25 @@ export const SYSTEM_AGENT_META: Record<string, AgentArchetype> = {
347
347
  // The Initiative Planning pipeline's two steps. Only runnable on an initiative
348
348
  // block (pl_initiative — enforced by the engine), so they are display-metadata
349
349
  // system kinds, never palette archetypes.
350
+ 'initiative-interviewer': {
351
+ kind: 'initiative-interviewer',
352
+ label: 'Initiative Interviewer',
353
+ icon: 'i-lucide-messages-square',
354
+ color: '#818cf8',
355
+ description:
356
+ 'Interviews you on the goals, scope and constraints of the initiative, then synthesizes the agreed brief the analyst and planner build on.',
357
+ // Opens the dedicated planning Q&A window (answer / continue / proceed) while parked.
358
+ resultView: 'initiative-planning',
359
+ },
360
+ 'initiative-analyst': {
361
+ kind: 'initiative-analyst',
362
+ label: 'Initiative Analyst',
363
+ icon: 'i-lucide-microscope',
364
+ color: '#818cf8',
365
+ description:
366
+ 'Explores the codebase and writes an analysis (architecture, touch points, risks) that grounds the plan. Makes no changes.',
367
+ resultView: 'initiative-tracker',
368
+ },
350
369
  'initiative-planner': {
351
370
  kind: 'initiative-planner',
352
371
  label: 'Initiative Planner',
@@ -59,7 +59,8 @@
59
59
  },
60
60
  "@clear": {
61
61
  "description": "Verb meaning empty / reset a stored value (a config, a saved connection), NOT the adjective 'transparent / obvious'. Used as a destructive-action button label."
62
- }
62
+ },
63
+ "undo": "Undo"
63
64
  },
64
65
  "nav": {
65
66
  "menu": "Navigation menu",
@@ -95,7 +96,9 @@
95
96
  "moveFailed": "Could not move",
96
97
  "deleteFailed": "Could not delete",
97
98
  "linkFailed": "Could not link tasks",
98
- "recurringDeleteFailed": "Could not delete recurring pipeline"
99
+ "recurringDeleteFailed": "Could not delete recurring pipeline",
100
+ "deleted": "Deleted \"{name}\"",
101
+ "moved": "Moved \"{name}\""
99
102
  },
100
103
  "repoTypes": {
101
104
  "service": "Service",
@@ -985,7 +988,8 @@
985
988
  "recurring": {
986
989
  "title": "Delete this recurring pipeline?",
987
990
  "body": "\"{name}\", its schedule and its run history will be removed. This can't be undone."
988
- }
991
+ },
992
+ "containerBodyWithCount": "\"{name}\" and the {count} item inside it will be removed. This can't be undone. | \"{name}\" and the {count} items inside it will be removed. This can't be undone."
989
993
  },
990
994
  "titlePlaceholder": "Title…",
991
995
  "reworkedRequirements": "Reworked requirements",
@@ -4135,7 +4139,19 @@
4135
4139
  },
4136
4140
  "inspector": {
4137
4141
  "runPlanning": "Run planning",
4142
+ "answerPlanning": "Answer planning questions",
4138
4143
  "hint": "The planning pipeline explores the codebase, drafts the multi-phase plan for approval, then commits the tracker document to the repository."
4144
+ },
4145
+ "planning": {
4146
+ "title": "Plan the initiative",
4147
+ "subtitle": "Answer the planner's questions so it can scope the initiative",
4148
+ "intro": "The planner is scoping this initiative. Answer its questions to shape the goal and constraints, then continue — or proceed to plan with what it has.",
4149
+ "empty": "No initiative found for this block.",
4150
+ "converged": "No questions are pending. The planner has what it needs and is drafting the plan.",
4151
+ "answerPlaceholder": "Your answer",
4152
+ "hint": "Continue lets the planner ask follow-ups; Proceed plans with the answers so far.",
4153
+ "proceed": "Proceed to plan",
4154
+ "continue": "Continue"
4139
4155
  }
4140
4156
  }
4141
4157
  }
@@ -50,7 +50,8 @@
50
50
  "revoked": "{name} revocado",
51
51
  "cleared": "{name} borrado",
52
52
  "destroyed": "{name} destruido"
53
- }
53
+ },
54
+ "undo": "Deshacer"
54
55
  },
55
56
  "nav": {
56
57
  "menu": "Menú de navegación",
@@ -80,7 +81,9 @@
80
81
  "moveFailed": "No se pudo mover",
81
82
  "deleteFailed": "No se pudo eliminar",
82
83
  "linkFailed": "No se pudieron vincular las tareas",
83
- "recurringDeleteFailed": "No se pudo eliminar el pipeline recurrente"
84
+ "recurringDeleteFailed": "No se pudo eliminar el pipeline recurrente",
85
+ "deleted": "\"{name}\" eliminado",
86
+ "moved": "\"{name}\" movido"
84
87
  },
85
88
  "repoTypes": {
86
89
  "service": "Servicio",
@@ -960,7 +963,8 @@
960
963
  "recurring": {
961
964
  "title": "¿Eliminar este pipeline recurrente?",
962
965
  "body": "Se eliminarán \"{name}\", su programación y su historial de ejecución. Esta acción no se puede deshacer."
963
- }
966
+ },
967
+ "containerBodyWithCount": "Se eliminará \"{name}\" y el {count} elemento que contiene. Esta acción no se puede deshacer. | Se eliminará \"{name}\" y los {count} elementos que contiene. Esta acción no se puede deshacer."
964
968
  }
965
969
  }
966
970
  },
@@ -4016,7 +4020,19 @@
4016
4020
  },
4017
4021
  "inspector": {
4018
4022
  "runPlanning": "Ejecutar planificacion",
4023
+ "answerPlanning": "Responder preguntas de planificacion",
4019
4024
  "hint": "El pipeline de planificacion explora el codigo, redacta el plan multifase para su aprobacion y luego confirma el documento de seguimiento en el repositorio."
4025
+ },
4026
+ "planning": {
4027
+ "title": "Planificar la iniciativa",
4028
+ "subtitle": "Responde las preguntas del planificador para acotar la iniciativa",
4029
+ "intro": "El planificador esta acotando esta iniciativa. Responde sus preguntas para definir el objetivo y las restricciones, luego continua, o procede a planificar con lo que tiene.",
4030
+ "empty": "No se encontro ninguna iniciativa para este bloque.",
4031
+ "converged": "No hay preguntas pendientes. El planificador tiene lo que necesita y esta redactando el plan.",
4032
+ "answerPlaceholder": "Tu respuesta",
4033
+ "hint": "Continuar permite al planificador hacer mas preguntas; Proceder planifica con las respuestas actuales.",
4034
+ "proceed": "Proceder a planificar",
4035
+ "continue": "Continuar"
4020
4036
  }
4021
4037
  }
4022
4038
  }
@@ -50,7 +50,8 @@
50
50
  "revoked": "{name} révoqué",
51
51
  "cleared": "{name} effacé",
52
52
  "destroyed": "{name} détruit"
53
- }
53
+ },
54
+ "undo": "Annuler"
54
55
  },
55
56
  "nav": {
56
57
  "menu": "Menu de navigation",
@@ -80,7 +81,9 @@
80
81
  "moveFailed": "Impossible de déplacer",
81
82
  "deleteFailed": "Impossible de supprimer",
82
83
  "linkFailed": "Impossible de lier les tâches",
83
- "recurringDeleteFailed": "Impossible de supprimer le pipeline récurrent"
84
+ "recurringDeleteFailed": "Impossible de supprimer le pipeline récurrent",
85
+ "deleted": "\"{name}\" supprimé",
86
+ "moved": "\"{name}\" déplacé"
84
87
  },
85
88
  "repoTypes": {
86
89
  "service": "Service",
@@ -960,7 +963,8 @@
960
963
  "recurring": {
961
964
  "title": "Supprimer ce pipeline récurrent ?",
962
965
  "body": "\"{name}\", sa planification et son historique d'exécution seront supprimés. Cette action est irréversible."
963
- }
966
+ },
967
+ "containerBodyWithCount": "\"{name}\" et le {count} élément qu'il contient seront supprimés. Cette action est irréversible. | \"{name}\" et les {count} éléments qu'il contient seront supprimés. Cette action est irréversible."
964
968
  }
965
969
  }
966
970
  },
@@ -4016,7 +4020,19 @@
4016
4020
  },
4017
4021
  "inspector": {
4018
4022
  "runPlanning": "Lancer la planification",
4023
+ "answerPlanning": "Repondre aux questions de planification",
4019
4024
  "hint": "Le pipeline de planification explore le code, redige le plan multiphase pour approbation, puis valide le document de suivi dans le depot."
4025
+ },
4026
+ "planning": {
4027
+ "title": "Planifier l'initiative",
4028
+ "subtitle": "Repondez aux questions du planificateur pour cadrer l'initiative",
4029
+ "intro": "Le planificateur cadre cette initiative. Repondez a ses questions pour definir l'objectif et les contraintes, puis continuez, ou procedez a la planification avec ce qu'il a.",
4030
+ "empty": "Aucune initiative trouvee pour ce bloc.",
4031
+ "converged": "Aucune question en attente. Le planificateur dispose de ce qu'il faut et redige le plan.",
4032
+ "answerPlaceholder": "Votre reponse",
4033
+ "hint": "Continuer permet au planificateur de poser des questions complementaires ; Proceder planifie avec les reponses actuelles.",
4034
+ "proceed": "Proceder a la planification",
4035
+ "continue": "Continuer"
4020
4036
  }
4021
4037
  }
4022
4038
  }
@@ -50,7 +50,8 @@
50
50
  "revoked": "{name} בוטל",
51
51
  "cleared": "{name} נוקה",
52
52
  "destroyed": "{name} הושמד"
53
- }
53
+ },
54
+ "undo": "בטל"
54
55
  },
55
56
  "nav": {
56
57
  "menu": "תפריט ניווט",
@@ -80,7 +81,9 @@
80
81
  "moveFailed": "לא ניתן היה להעביר",
81
82
  "deleteFailed": "לא ניתן היה למחוק",
82
83
  "linkFailed": "לא ניתן היה לקשר משימות",
83
- "recurringDeleteFailed": "לא ניתן היה למחוק את הצינור החוזר"
84
+ "recurringDeleteFailed": "לא ניתן היה למחוק את הצינור החוזר",
85
+ "deleted": "\"{name}\" נמחק",
86
+ "moved": "\"{name}\" הועבר"
84
87
  },
85
88
  "repoTypes": {
86
89
  "service": "שירות",
@@ -960,7 +963,8 @@
960
963
  "recurring": {
961
964
  "title": "למחוק את ה־pipeline החוזר הזה?",
962
965
  "body": "\"{name}\", התזמון שלו והיסטוריית ההרצות יימחקו. לא ניתן לבטל פעולה זו."
963
- }
966
+ },
967
+ "containerBodyWithCount": "\"{name}\" ו-{count} פריט שבתוכו יימחקו. לא ניתן לבטל פעולה זו. | \"{name}\" ו-{count} פריטים שבתוכו יימחקו. לא ניתן לבטל פעולה זו."
964
968
  }
965
969
  }
966
970
  },
@@ -4027,7 +4031,19 @@
4027
4031
  },
4028
4032
  "inspector": {
4029
4033
  "runPlanning": "הרצת תכנון",
4034
+ "answerPlanning": "מענה על שאלות התכנון",
4030
4035
  "hint": "צינור התכנון חוקר את הקוד, מנסח את התוכנית הרב-שלבית לאישור, ואז שומר את מסמך המעקב במאגר."
4036
+ },
4037
+ "planning": {
4038
+ "title": "תכנון היוזמה",
4039
+ "subtitle": "ענה על שאלות המתכנן כדי למקד את היוזמה",
4040
+ "intro": "המתכנן ממקד את היוזמה. ענה על שאלותיו כדי לעצב את המטרה והאילוצים, ואז המשך — או עבור לתכנון עם מה שיש לו.",
4041
+ "empty": "לא נמצאה יוזמה עבור בלוק זה.",
4042
+ "converged": "אין שאלות ממתינות. למתכנן יש את מה שנדרש והוא מנסח את התוכנית.",
4043
+ "answerPlaceholder": "התשובה שלך",
4044
+ "hint": "המשך מאפשר למתכנן לשאול שאלות המשך; עבור לתכנון מתכנן עם התשובות עד כה.",
4045
+ "proceed": "עבור לתכנון",
4046
+ "continue": "המשך"
4031
4047
  }
4032
4048
  }
4033
4049
  }
@@ -50,7 +50,8 @@
50
50
  "revoked": "{name} を取り消しました",
51
51
  "cleared": "{name} をクリアしました",
52
52
  "destroyed": "{name} を破棄しました"
53
- }
53
+ },
54
+ "undo": "元に戻す"
54
55
  },
55
56
  "nav": {
56
57
  "menu": "ナビゲーションメニュー",
@@ -80,7 +81,9 @@
80
81
  "moveFailed": "移動できませんでした",
81
82
  "deleteFailed": "削除できませんでした",
82
83
  "linkFailed": "タスクをリンクできませんでした",
83
- "recurringDeleteFailed": "定期パイプラインを削除できませんでした"
84
+ "recurringDeleteFailed": "定期パイプラインを削除できませんでした",
85
+ "deleted": "\"{name}\" を削除しました",
86
+ "moved": "\"{name}\" を移動しました"
84
87
  },
85
88
  "repoTypes": {
86
89
  "service": "サービス",
@@ -960,7 +963,8 @@
960
963
  "recurring": {
961
964
  "title": "この定期パイプラインを削除しますか?",
962
965
  "body": "「{name}」、そのスケジュール、実行履歴が削除されます。この操作は取り消せません。"
963
- }
966
+ },
967
+ "containerBodyWithCount": "「{name}」とその中の {count} 件の項目が削除されます。この操作は取り消せません。 | 「{name}」とその中の {count} 件の項目が削除されます。この操作は取り消せません。"
964
968
  }
965
969
  }
966
970
  },
@@ -4029,7 +4033,19 @@
4029
4033
  },
4030
4034
  "inspector": {
4031
4035
  "runPlanning": "計画を実行",
4036
+ "answerPlanning": "計画の質問に回答",
4032
4037
  "hint": "計画パイプラインはコードベースを調査し、承認用の複数フェーズ計画を起草し、その後トラッカー文書をリポジトリにコミットします。"
4038
+ },
4039
+ "planning": {
4040
+ "title": "イニシアチブを計画",
4041
+ "subtitle": "プランナーの質問に答えてイニシアチブの範囲を定めます",
4042
+ "intro": "プランナーがこのイニシアチブの範囲を検討しています。質問に答えて目標と制約を形にし、続行するか、現状のまま計画に進んでください。",
4043
+ "empty": "このブロックのイニシアチブが見つかりません。",
4044
+ "converged": "保留中の質問はありません。プランナーは必要な情報を得て計画を作成しています。",
4045
+ "answerPlaceholder": "回答",
4046
+ "hint": "「続行」でプランナーが追加の質問をします。「計画に進む」でこれまでの回答をもとに計画します。",
4047
+ "proceed": "計画に進む",
4048
+ "continue": "続行"
4033
4049
  }
4034
4050
  }
4035
4051
  }