@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.
- package/app/components/initiative/InitiativePlanningWindow.vue +189 -0
- package/app/components/panels/StepResultViewHost.vue +2 -0
- package/app/components/panels/inspector/InitiativeInspector.vue +21 -0
- package/app/composables/api/initiative.ts +29 -0
- package/app/composables/useBlockDeletion.ts +21 -5
- package/app/composables/useBlockQueries.ts +18 -0
- package/app/stores/board.spec.ts +196 -1
- package/app/stores/board.ts +156 -22
- package/app/stores/execution.ts +7 -3
- package/app/stores/initiative.ts +58 -1
- package/app/stores/ui.ts +6 -0
- package/app/utils/catalog.ts +19 -0
- package/i18n/locales/en.json +19 -3
- package/i18n/locales/es.json +19 -3
- package/i18n/locales/fr.json +19 -3
- package/i18n/locales/he.json +19 -3
- package/i18n/locales/ja.json +19 -3
- package/i18n/locales/pl.json +19 -3
- package/i18n/locales/tr.json +19 -3
- package/i18n/locales/uk.json +19 -3
- package/package.json +2 -2
package/app/stores/board.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
298
|
-
|
|
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
|
-
*
|
|
307
|
-
* toast
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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: '
|
|
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',
|
package/app/stores/execution.ts
CHANGED
|
@@ -304,10 +304,14 @@ export const useExecutionStore = defineStore('execution', () => {
|
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
/**
|
|
308
|
-
|
|
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
|
}
|
package/app/stores/initiative.ts
CHANGED
|
@@ -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 {
|
|
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,
|
package/app/utils/catalog.ts
CHANGED
|
@@ -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',
|
package/i18n/locales/en.json
CHANGED
|
@@ -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
|
}
|
package/i18n/locales/es.json
CHANGED
|
@@ -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
|
}
|
package/i18n/locales/fr.json
CHANGED
|
@@ -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
|
}
|
package/i18n/locales/he.json
CHANGED
|
@@ -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
|
}
|
package/i18n/locales/ja.json
CHANGED
|
@@ -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
|
}
|