@cat-factory/app 0.185.0 → 0.186.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 (32) hide show
  1. package/README.md +4 -1
  2. package/app/assets/css/main.css +2 -0
  3. package/app/assets/css/prose.css +135 -0
  4. package/app/components/board/AddTaskModal.vue +31 -1
  5. package/app/components/brainstorm/BrainstormWindow.vue +4 -37
  6. package/app/components/clarity/ClarityReviewWindow.vue +4 -37
  7. package/app/components/initiative/InitiativePlanReview.vue +351 -0
  8. package/app/components/initiative/InitiativeTrackerWindow.vue +12 -126
  9. package/app/components/panels/AgentStepDetail.vue +19 -122
  10. package/app/components/panels/inspector/TaskReviewTarget.vue +69 -0
  11. package/app/components/requirements/RequirementsReviewWindow.vue +4 -37
  12. package/app/composables/useProseComments.ts +126 -0
  13. package/app/composables/useStepApproval.ts +29 -85
  14. package/app/composables/useStepProse.spec.ts +67 -0
  15. package/app/composables/useStepProse.ts +22 -10
  16. package/app/modular/nav-contributions.spec.ts +3 -13
  17. package/app/modular/panels/inspector.logic.spec.ts +7 -0
  18. package/app/modular/panels/inspector.logic.ts +5 -0
  19. package/app/modular/panels/inspector.ts +2 -0
  20. package/app/stores/execution.ts +9 -0
  21. package/app/utils/initiative.spec.ts +24 -0
  22. package/i18n/locales/de.json +14 -3
  23. package/i18n/locales/en.json +14 -3
  24. package/i18n/locales/es.json +14 -3
  25. package/i18n/locales/fr.json +14 -3
  26. package/i18n/locales/he.json +14 -3
  27. package/i18n/locales/it.json +14 -3
  28. package/i18n/locales/ja.json +14 -3
  29. package/i18n/locales/pl.json +14 -3
  30. package/i18n/locales/tr.json +14 -3
  31. package/i18n/locales/uk.json +14 -3
  32. package/package.json +2 -2
@@ -1,23 +1,17 @@
1
- import { ref, computed, nextTick, watch } from 'vue'
1
+ import { ref, computed, nextTick } from 'vue'
2
2
  import type { PipelineStep } from '~/types/execution'
3
- import { sliceSource } from '~/utils/agentOutput'
4
-
5
- /** A draft per-block review comment, anchored to a source range of the output. */
6
- interface DraftComment {
7
- srcStart: number
8
- srcEnd: number
9
- quotedSource: string
10
- body: string
11
- }
3
+ import { useProseComments } from '~/composables/useProseComments'
12
4
 
13
5
  /**
14
6
  * The GitHub-style approval/review state machine for a pending gate step. When the
15
7
  * step's gate is pending the prose reader doubles as a review surface: the human can
16
8
  * comment on individual source-mapped blocks, leave overall feedback, edit the
17
9
  * conclusions in place, then Approve / Request changes / Reject. This composable owns
18
- * all of that draft state + the in-document highlight syncing; the parent supplies the
19
- * live step, the scroll container (for highlight lookups), the run/approval ids, and a
20
- * `close` callback the actions invoke once they resolve.
10
+ * the approval-API half plus the edit/reject sub-states; the per-block comment drafts and
11
+ * their in-document highlighting live in {@link useProseComments}, which the initiative
12
+ * tracker's plan-approval rail shares. The parent supplies the live step, the scroll
13
+ * container (for highlight lookups), the run/approval ids, and a `close` callback the
14
+ * actions invoke once they resolve.
21
15
  */
22
16
  export function useStepApproval(opts: {
23
17
  step: () => PipelineStep | null
@@ -30,11 +24,8 @@ export function useStepApproval(opts: {
30
24
  }) {
31
25
  const execution = useExecutionStore()
32
26
 
33
- const reviewComments = ref<DraftComment[]>([])
34
27
  const feedback = ref('')
35
28
  const submitting = ref(false)
36
- const draftTarget = ref<{ srcStart: number; srcEnd: number; quotedSource: string } | null>(null)
37
- const draftBody = ref('')
38
29
 
39
30
  // "Approve with corrections" mode: a deliberate state distinct from the read-only
40
31
  // review — the human edits the conclusions directly and those edits flow forward as
@@ -45,56 +36,25 @@ export function useStepApproval(opts: {
45
36
  // Reject stops the whole run, so it's a two-step inline confirm (no native dialog).
46
37
  const rejectArmed = ref(false)
47
38
 
48
- const blockKey = (c: { srcStart: number; srcEnd: number }) => `${c.srcStart}:${c.srcEnd}`
49
-
50
- /** Toggle the highlight classes on commented / selected blocks within the reader. */
51
- function syncHighlights() {
52
- const root = opts.scrollEl()
53
- if (!root) return
54
- const commented = new Set(reviewComments.value.map(blockKey))
55
- const selected = draftTarget.value ? blockKey(draftTarget.value) : null
56
- for (const el of Array.from(root.querySelectorAll('[data-src-start]'))) {
57
- const key = `${el.getAttribute('data-src-start')}:${el.getAttribute('data-src-end')}`
58
- el.classList.toggle('cf-commented', commented.has(key))
59
- el.classList.toggle('cf-selected', key === selected)
60
- }
61
- }
62
-
63
- /** Click a rendered block to start commenting on it (links keep working). */
64
- function onProseClick(e: MouseEvent) {
65
- if (!opts.approvalPending() || opts.companionExceeded() || editing.value) return
66
- const target = e.target as HTMLElement
67
- if (target.closest('a')) return
68
- const blockEl = target.closest('[data-src-start]') as HTMLElement | null
69
- if (!blockEl) return
70
- const srcStart = Number(blockEl.getAttribute('data-src-start'))
71
- const srcEnd = Number(blockEl.getAttribute('data-src-end'))
72
- if (Number.isNaN(srcStart) || Number.isNaN(srcEnd)) return
73
- draftTarget.value = {
74
- srcStart,
75
- srcEnd,
76
- quotedSource: sliceSource(opts.step()?.output ?? '', srcStart, srcEnd),
77
- }
78
- draftBody.value = ''
79
- void nextTick(syncHighlights)
80
- }
81
-
82
- function addDraftComment() {
83
- if (!draftTarget.value || !draftBody.value.trim()) return
84
- reviewComments.value.push({ ...draftTarget.value, body: draftBody.value.trim() })
85
- draftTarget.value = null
86
- draftBody.value = ''
87
- void nextTick(syncHighlights)
88
- }
89
- function cancelDraft() {
90
- draftTarget.value = null
91
- draftBody.value = ''
92
- void nextTick(syncHighlights)
93
- }
94
- function removeComment(idx: number) {
95
- reviewComments.value.splice(idx, 1)
96
- void nextTick(syncHighlights)
97
- }
39
+ // The per-block comment drafts + their in-document highlights. Commenting is off while the
40
+ // human is in edit mode: the two paths are mutually exclusive, and the editor replaces the
41
+ // rendered document with a textarea, so there are no blocks to anchor to.
42
+ const prose = useProseComments({
43
+ output: () => opts.step()?.output ?? '',
44
+ root: () => opts.scrollEl(),
45
+ enabled: () => opts.approvalPending() && !opts.companionExceeded() && !editing.value,
46
+ })
47
+ const {
48
+ comments: reviewComments,
49
+ wireComments,
50
+ draftTarget,
51
+ draftBody,
52
+ syncHighlights,
53
+ onProseClick,
54
+ addDraftComment,
55
+ cancelDraft,
56
+ removeComment,
57
+ } = prose
98
58
 
99
59
  const canRequestChanges = computed(
100
60
  () => !!feedback.value.trim() || reviewComments.value.length > 0,
@@ -119,7 +79,7 @@ export function useStepApproval(opts: {
119
79
  editing.value = true
120
80
  // Editing and the review/reject path are mutually exclusive — clear the other.
121
81
  rejectArmed.value = false
122
- draftTarget.value = null
82
+ prose.cancelDraft()
123
83
  void nextTick(syncHighlights)
124
84
  }
125
85
  function cancelEditing() {
@@ -143,14 +103,7 @@ export function useStepApproval(opts: {
143
103
  try {
144
104
  const ok = await execution.requestStepChanges(opts.instanceId()!, id, {
145
105
  feedback: feedback.value.trim() || undefined,
146
- comments: reviewComments.value.length
147
- ? reviewComments.value.map((c) => ({
148
- quotedSource: c.quotedSource,
149
- srcStart: c.srcStart,
150
- srcEnd: c.srcEnd,
151
- body: c.body,
152
- }))
153
- : undefined,
106
+ comments: wireComments.value,
154
107
  })
155
108
  if (ok) opts.close()
156
109
  } finally {
@@ -189,22 +142,13 @@ export function useStepApproval(opts: {
189
142
 
190
143
  /** Full reset of every draft when a different gate/step opens. */
191
144
  function resetForStep() {
192
- reviewComments.value = []
145
+ prose.reset()
193
146
  feedback.value = ''
194
- draftTarget.value = null
195
- draftBody.value = ''
196
147
  rejectArmed.value = false
197
148
  editing.value = false
198
149
  draftProposal.value = ''
199
150
  }
200
151
 
201
- // Keep the in-document highlights in sync as the output renders or comments change.
202
- watch(
203
- [opts.approvalPending, () => opts.step()?.output, reviewComments, draftTarget],
204
- () => void nextTick(syncHighlights),
205
- { deep: true },
206
- )
207
-
208
152
  return {
209
153
  reviewComments,
210
154
  feedback,
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { useStepProse } from './useStepProse'
3
+
4
+ const DOC = ['# Plan', '', 'Intro.', '', '## Phase 1', '', 'First.', '', '## Phase 2', ''].join(
5
+ '\n',
6
+ )
7
+
8
+ /**
9
+ * A fake measurable element: the scroll-spy only ever reads `getBoundingClientRect().top`, so
10
+ * the vertical position is the whole of what a section is to it.
11
+ */
12
+ function sectionAt(top: number): HTMLElement {
13
+ return { getBoundingClientRect: () => ({ top }) } as unknown as HTMLElement
14
+ }
15
+
16
+ describe('useStepProse scroll-spy', () => {
17
+ /**
18
+ * The reader's own layout: a details card ahead of the prose, which the consumer registers.
19
+ */
20
+ it('tracks the last anchor above the fold, lead section included', () => {
21
+ const prose = useStepProse(() => DOC)
22
+ prose.scrollEl.value = sectionAt(0)
23
+ const [first, second] = prose.tocSections.value
24
+ prose.sectionEls['step-details'] = sectionAt(-200)
25
+ prose.sectionEls[first!.id] = sectionAt(-100)
26
+ prose.sectionEls[second!.id] = sectionAt(400)
27
+
28
+ prose.onScroll()
29
+ expect(prose.activeId.value).toBe(first!.id)
30
+
31
+ // Scrolling on past the second heading moves the highlight to it.
32
+ prose.sectionEls[second!.id] = sectionAt(20)
33
+ prose.onScroll()
34
+ expect(prose.activeId.value).toBe(second!.id)
35
+ })
36
+
37
+ /**
38
+ * The regression this option exists for. A consumer that renders the document ALONE (the
39
+ * initiative tracker's plan-approval rail) never registers a lead anchor, and the spy walks
40
+ * anchors in document order and stops at the first one it cannot measure — so with the lead
41
+ * anchor hardcoded it stopped immediately, pinning `activeId` to a section that does not
42
+ * exist. Nothing threw; the ToC simply never highlighted anything, and any click-to-navigate
43
+ * highlight was wiped by the scroll event the smooth scroll itself fires.
44
+ */
45
+ it('tracks sections when the consumer renders no lead anchor', () => {
46
+ const prose = useStepProse(() => DOC, { leadAnchorId: null })
47
+ prose.scrollEl.value = sectionAt(0)
48
+ const [first, second] = prose.tocSections.value
49
+ prose.sectionEls[first!.id] = sectionAt(-100)
50
+ prose.sectionEls[second!.id] = sectionAt(400)
51
+
52
+ prose.onScroll()
53
+ expect(prose.activeId.value).toBe(first!.id)
54
+ })
55
+
56
+ it('starts on the lead anchor, or on nothing when there is none', () => {
57
+ expect(useStepProse(() => DOC).activeId.value).toBe('step-details')
58
+ expect(useStepProse(() => DOC, { leadAnchorId: null }).activeId.value).toBe('')
59
+ })
60
+
61
+ it('re-seeds the active anchor on reset', () => {
62
+ const prose = useStepProse(() => DOC, { leadAnchorId: null })
63
+ prose.activeId.value = 'somewhere'
64
+ prose.reset()
65
+ expect(prose.activeId.value).toBe('')
66
+ })
67
+ })
@@ -4,23 +4,35 @@ import { parseOutputOutline } from '~/utils/agentOutput'
4
4
  /**
5
5
  * The prose reader for an agent step's markdown output: its heading outline, the
6
6
  * per-section collapse state, and the scroll-spy that keeps the ToC in sync.
7
- * Owns the scroll container + per-section element refs the template binds; the
8
- * details card is always the first anchor. `reset()` re-seeds (all sections
9
- * expanded, scrolled to top) whenever a different step opens.
7
+ * Owns the scroll container + per-section element refs the template binds.
8
+ * `reset()` re-seeds (all sections expanded, scrolled to top) whenever a different
9
+ * step opens.
10
+ *
11
+ * `leadAnchorId` names a section the CONSUMER renders ahead of the prose and registers in
12
+ * `sectionEls` — the step reader's details card. It is an option rather than a constant
13
+ * because the scroll-spy walks the anchors in document order and stops at the first one it
14
+ * cannot measure: a consumer that renders no such element (the initiative tracker's
15
+ * plan-approval rail, which has the document alone) would otherwise have its spy stop dead on
16
+ * an anchor that never exists, pinning `activeId` to a section nobody can see and leaving the
17
+ * ToC with nothing highlighted. Pass `null` when the prose is the whole document.
10
18
  */
11
- export function useStepProse(getOutput: () => string) {
19
+ export function useStepProse(getOutput: () => string, opts: { leadAnchorId?: string | null } = {}) {
20
+ const leadAnchorId = opts.leadAnchorId === undefined ? 'step-details' : opts.leadAnchorId
12
21
  const outline = computed(() => parseOutputOutline(getOutput()))
13
22
  const tocSections = computed(() => outline.value.sections.filter((s) => s.depth > 0))
14
23
  const hasOutput = computed(() => !!getOutput().trim())
15
24
 
16
25
  const collapsed = reactive<Record<string, boolean>>({})
17
- const activeId = ref<string>('step-details')
26
+ const activeId = ref<string>(leadAnchorId ?? '')
18
27
  const scrollEl = ref<HTMLElement | null>(null)
19
28
  const sectionEls = reactive<Record<string, HTMLElement | null>>({})
20
29
 
21
- // Anchors the ToC navigates + the scroll-spy tracks: the details card first, then
22
- // every heading section of the prose.
23
- const anchors = computed(() => ['step-details', ...tocSections.value.map((s) => s.id)])
30
+ // Anchors the ToC navigates + the scroll-spy tracks: the lead section (when the consumer
31
+ // renders one) first, then every heading section of the prose.
32
+ const anchors = computed(() => [
33
+ ...(leadAnchorId ? [leadAnchorId] : []),
34
+ ...tocSections.value.map((s) => s.id),
35
+ ])
24
36
 
25
37
  function toggle(id: string) {
26
38
  collapsed[id] = !collapsed[id]
@@ -43,7 +55,7 @@ export function useStepProse(getOutput: () => string) {
43
55
  const container = scrollEl.value
44
56
  if (!container) return
45
57
  const line = container.getBoundingClientRect().top + 80
46
- let current = anchors.value[0] ?? 'step-details'
58
+ let current = anchors.value[0] ?? ''
47
59
  for (const id of anchors.value) {
48
60
  const el = sectionEls[id]
49
61
  if (el && el.getBoundingClientRect().top <= line) current = id
@@ -55,7 +67,7 @@ export function useStepProse(getOutput: () => string) {
55
67
  // Re-seed (all sections expanded, scrolled to top) for a freshly-opened step.
56
68
  function reset() {
57
69
  for (const k of Object.keys(collapsed)) delete collapsed[k]
58
- activeId.value = 'step-details'
70
+ activeId.value = leadAnchorId ?? ''
59
71
  void nextTick(() => scrollEl.value?.scrollTo({ top: 0 }))
60
72
  }
61
73
 
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import enCatalog from '../../i18n/locales/en.json'
2
+ import { hasI18nKey } from '../../test/i18nKeys'
3
3
  import {
4
4
  groupCommands,
5
5
  groupSidebar,
@@ -10,18 +10,8 @@ import {
10
10
  } from './nav-contributions'
11
11
  import type { AppSlots, NavGates } from './nav-contributions'
12
12
 
13
- /** The layer's base i18n catalog, used to prove every referenced key resolves. */
14
- const en = enCatalog as Record<string, unknown>
15
-
16
- /** Walk a dotted vue-i18n key path; true when it resolves to a leaf string. */
17
- function hasKey(path: string): boolean {
18
- let node: unknown = en
19
- for (const part of path.split('.')) {
20
- if (typeof node !== 'object' || node === null || !(part in node)) return false
21
- node = (node as Record<string, unknown>)[part]
22
- }
23
- return typeof node === 'string'
24
- }
13
+ /** Prove every referenced key resolves in the layer's base catalog (see `test/i18nKeys`). */
14
+ const hasKey = hasI18nKey
25
15
 
26
16
  const NO_GATES: NavGates = {
27
17
  canWriteBoard: false,
@@ -80,6 +80,13 @@ describe('inspector panel group', () => {
80
80
  expect(visibleIds(block('module'))).toEqual(['container-summary'])
81
81
  })
82
82
 
83
+ // The reviewed PR is the review task's SUBJECT, so it leads the body — above the context the
84
+ // task was given and the run that acts on it. Every other task type never sees the panel.
85
+ it('a review task leads with its review target', () => {
86
+ const review = { ...block('task'), taskType: 'review' } as Block
87
+ expect(visibleIds(review)[0]).toBe('task-review-target')
88
+ })
89
+
83
90
  it('a task shows the task body in the pre-slice-4 order', () => {
84
91
  expect(visibleIds(block('task'))).toEqual([
85
92
  'task-context-docs',
@@ -36,6 +36,7 @@ export const inspectorPanels = definePanelGroup<Block>(INSPECTOR_PANELS_SLOT)
36
36
  * a silently-dropped panel). */
37
37
  export const INSPECTOR_PANEL_IDS = [
38
38
  // task body (rendered in this order under the identity block)
39
+ 'task-review-target',
39
40
  'task-context-docs',
40
41
  'task-context-issues',
41
42
  'recurring-schedule',
@@ -119,6 +120,10 @@ const isDeployableFrame = (b: Block) => isFrame(b) && b.type !== 'document'
119
120
  * extensibility value.
120
121
  */
121
122
  export const INSPECTOR_PANEL_SPECS: readonly InspectorPanelSpec[] = [
123
+ // FIRST in the task body: for a review task the reviewed PR is what the task IS, so it reads
124
+ // above the context it was given and the run that acts on it. Gated on the task TYPE alone —
125
+ // the panel itself hides when the task carries no PR reference.
126
+ { id: 'task-review-target', order: 5, when: (b) => isTask(b) && b.taskType === 'review' },
122
127
  // Shared with the initiative body — an initiative takes the same attachments a task does.
123
128
  { id: 'task-context-docs', order: 10, when: takesContext },
124
129
  { id: 'task-context-issues', order: 20, when: takesContext },
@@ -16,6 +16,7 @@ import TaskContextDocs from '~/components/documents/TaskContextDocs.vue'
16
16
  import TaskContextIssues from '~/components/tasks/TaskContextIssues.vue'
17
17
  import RecurringScheduleSettings from '~/components/panels/inspector/RecurringScheduleSettings.vue'
18
18
  import TaskExecution from '~/components/panels/inspector/TaskExecution.vue'
19
+ import TaskReviewTarget from '~/components/panels/inspector/TaskReviewTarget.vue'
19
20
  import TaskEstimateBadge from '~/components/panels/inspector/TaskEstimateBadge.vue'
20
21
  import TaskDependencies from '~/components/panels/inspector/TaskDependencies.vue'
21
22
  import TaskRunSettings from '~/components/panels/inspector/TaskRunSettings.vue'
@@ -65,6 +66,7 @@ function blockPanel(component: Component, id: InspectorPanelId): PanelComponent
65
66
  /** Exhaustive id → sub-panel map. Typed `Record<InspectorPanelId, …>` so adding a
66
67
  * spec without a component (or vice-versa) fails the typecheck. */
67
68
  const COMPONENTS: Record<InspectorPanelId, Component> = {
69
+ 'task-review-target': TaskReviewTarget,
68
70
  'task-context-docs': TaskContextDocs,
69
71
  'task-context-issues': TaskContextIssues,
70
72
  'recurring-schedule': RecurringScheduleSettings,
@@ -188,6 +188,14 @@ export const useExecutionStore = defineStore('execution', () => {
188
188
  blockId: string
189
189
  approval: StepApproval
190
190
  agentKind: PipelineStep['agentKind']
191
+ /**
192
+ * Whether the gate's proposal is a RENDERING of an artifact the step already committed
193
+ * (`step.outputIsRendered`). Projected here because a surface that reviews the proposal
194
+ * WITHOUT the step in hand — the initiative tracker's plan-approval rail — otherwise has
195
+ * no way to tell a rendered document from the agent's raw transcript summary, and would
196
+ * present a one-line summary as though it were the artifact.
197
+ */
198
+ outputIsRendered: boolean
191
199
  }[] = []
192
200
  for (const e of instances.value) {
193
201
  for (const s of e.steps) {
@@ -197,6 +205,7 @@ export const useExecutionStore = defineStore('execution', () => {
197
205
  blockId: e.blockId,
198
206
  approval: s.approval,
199
207
  agentKind: s.agentKind,
208
+ outputIsRendered: s.outputIsRendered === true,
200
209
  })
201
210
  }
202
211
  }
@@ -1,7 +1,11 @@
1
1
  import { INITIATIVE_ITEM_TERMINAL_STATUSES } from '@cat-factory/contracts'
2
2
  import { describe, it, expect } from 'vitest'
3
3
  import type { InitiativeItem, InitiativePhase, InitiativeQa } from '~/types/domain'
4
+ import { missingI18nKeys } from '../../test/i18nKeys'
4
5
  import {
6
+ INITIATIVE_ATTENTION_LABEL_KEYS,
7
+ INITIATIVE_FOLLOWUP_STATUS_LABEL_KEYS,
8
+ INITIATIVE_STATUS_LABEL_KEYS,
5
9
  isPendingQuestion,
6
10
  orderInterviewQuestions,
7
11
  pendingCheckpointPhase,
@@ -200,3 +204,23 @@ describe('selectPlanApproval', () => {
200
204
  expect(selectPlanApproval(approvals, resultViewOf)?.approval.id).toBe('ap_custom')
201
205
  })
202
206
  })
207
+
208
+ /**
209
+ * These tables are the reason the initiative card and the inspector word one park identically,
210
+ * and they are exactly the shape both i18n drift guards are blind to: the typed-key check and
211
+ * `i18n:check` only see a key written literally at a `t()` call site, while the exhaustive
212
+ * `Record` only proves every enum MEMBER has an entry — never that the entry still names a key
213
+ * the catalog holds. Without this, deleting a key reads as a clean removal and the affordance
214
+ * renders its own key path to the user.
215
+ */
216
+ describe('the initiative label-key tables', () => {
217
+ it('name keys the base catalog actually holds', () => {
218
+ expect(
219
+ missingI18nKeys([
220
+ ...Object.values(INITIATIVE_ATTENTION_LABEL_KEYS),
221
+ ...Object.values(INITIATIVE_STATUS_LABEL_KEYS),
222
+ ...Object.values(INITIATIVE_FOLLOWUP_STATUS_LABEL_KEYS),
223
+ ]),
224
+ ).toEqual([])
225
+ })
226
+ })
@@ -1285,6 +1285,12 @@
1285
1285
  "risk": "Risiko",
1286
1286
  "impact": "Wirkung"
1287
1287
  },
1288
+ "reviewTarget": {
1289
+ "title": "Geprüfter Pull Request",
1290
+ "hint": "Der bestehende Pull Request, den diese Aufgabe prüft.",
1291
+ "prNumber": "PR #{number}",
1292
+ "focus": "Schwerpunkt: {focus}"
1293
+ },
1288
1294
  "execution": {
1289
1295
  "title": "Ausführung",
1290
1296
  "hint": "Der Live-Lauf der Pipeline: jeder Agent-Schritt, sein Fortschritt und der resultierende Pull Request.",
@@ -1625,6 +1631,7 @@
1625
1631
  "editConclusionsPlaceholder": "Bearbeite die Schlussfolgerungen des Agents; deine Änderungen werden bei der Freigabe gespeichert…",
1626
1632
  "noProseOutput": "Dieser Agent hat keine Prosa-Ausgabe erzeugt.",
1627
1633
  "approveWithCorrections": "Mit Korrekturen freigeben",
1634
+ "renderedOutputNote": "Diese Ausgabe ist eine Darstellung dessen, was der Schritt erzeugt hat, und kann hier nicht bearbeitet werden. Fordere Änderungen an, um sie überarbeiten zu lassen.",
1628
1635
  "reviewAndApprove": "Prüfen & freigeben",
1629
1636
  "editHint": "Bearbeite die Schlussfolgerungen links; deine Änderungen werden bei der Freigabe gespeichert.",
1630
1637
  "reviewHint": "Klicke auf einen beliebigen Block in der Ausgabe, um ihn zu kommentieren, oder hinterlasse unten allgemeines Feedback.",
@@ -2424,7 +2431,9 @@
2424
2431
  "focus": "Prüfungsschwerpunkt",
2425
2432
  "focusPlaceholder": "z. B. Fokus auf die Auth-Änderungen und Fehlerbehandlung",
2426
2433
  "derivedTitle": "{ref} prüfen",
2427
- "derivedTitleFallback": "Pull Request prüfen"
2434
+ "derivedTitleFallback": "Pull Request prüfen",
2435
+ "prNotFound": "Pull Request #{number} wurde im Repository dieses Service nicht gefunden. Prüfe die Nummer, oder verknüpfe den Service mit dem Repository, in dem der Pull Request liegt.",
2436
+ "prRepoMismatch": "Dieser Pull Request liegt in einem anderen Repository. Dieser Service prüft {repo}; lege die Prüfaufgabe unter dem Service an, der mit dem Repository des Pull Requests verknüpft ist."
2428
2437
  }
2429
2438
  },
2430
2439
  "recurring": {
@@ -4332,9 +4341,11 @@
4332
4341
  "title": "Dieser Plan wartet auf dich",
4333
4342
  "body": "Der Planner hat die Phasen und Aufgaben unten entworfen. Gib sie frei, um den Plan zu committen und die Arbeit zu starten, oder schicke den Plan mit deinen Änderungswünschen zurück.",
4334
4343
  "approve": "Plan freigeben",
4335
- "requestChanges": "Änderungen anfordern",
4336
4344
  "feedbackPlaceholder": "Was soll der Planner ändern? Umfang, Reihenfolge der Phasen, fehlende Arbeit, eine Aufgabe, die woanders hingehört …",
4337
- "sendBack": "An den Planner zurückschicken"
4345
+ "sendBack": "An den Planner zurückschicken",
4346
+ "noDocument": "Dieser Plan entstand, bevor die Plattform Pläne zur Prüfung gerendert hat — es gibt kein Dokument zum Navigieren; die Abschnitte unten sind der Plan.",
4347
+ "needsFeedback": "Füge zuerst einen Kommentar oder eine Rückmeldung hinzu — der Planer plant damit neu.",
4348
+ "commentHint": "Klicke auf einen Teil des Plans, um ihn zu kommentieren."
4338
4349
  },
4339
4350
  "planning": {
4340
4351
  "title": "Die Initiative planen",
@@ -326,7 +326,9 @@
326
326
  "focus": "Review focus",
327
327
  "focusPlaceholder": "e.g. focus on the auth changes and error handling",
328
328
  "derivedTitle": "Review {ref}",
329
- "derivedTitleFallback": "Review pull request"
329
+ "derivedTitleFallback": "Review pull request",
330
+ "prNotFound": "Pull request #{number} was not found in this service's repository. Check the number, or link the service to the repository that pull request is on.",
331
+ "prRepoMismatch": "That pull request is on a different repository. This service reviews {repo}, so create the review task under the service linked to the pull request's repository."
330
332
  }
331
333
  },
332
334
  "recurring": {
@@ -1007,6 +1009,12 @@
1007
1009
  "risk": "Risk",
1008
1010
  "impact": "Impact"
1009
1011
  },
1012
+ "reviewTarget": {
1013
+ "title": "Under review",
1014
+ "hint": "The existing pull request this task reviews.",
1015
+ "prNumber": "PR #{number}",
1016
+ "focus": "Focus: {focus}"
1017
+ },
1010
1018
  "execution": {
1011
1019
  "title": "Execution",
1012
1020
  "hint": "The live pipeline run: each agent step, its progress, and the resulting pull request.",
@@ -1348,6 +1356,7 @@
1348
1356
  "editConclusionsPlaceholder": "Edit the agent's conclusions; your edits are saved when you approve…",
1349
1357
  "noProseOutput": "This agent produced no prose output.",
1350
1358
  "approveWithCorrections": "Approve with corrections",
1359
+ "renderedOutputNote": "This output is a rendering of what the step produced, so it cannot be edited here. Request changes to have it revised.",
1351
1360
  "reviewAndApprove": "Review & approve",
1352
1361
  "editHint": "Edit the conclusions on the left; your edits are saved when you approve.",
1353
1362
  "reviewHint": "Click any block in the output to comment on it, or leave overall feedback below.",
@@ -5532,9 +5541,11 @@
5532
5541
  "title": "This plan is waiting for you",
5533
5542
  "body": "The planner drafted the phases and items below. Approve them to commit the plan and start the work, or send the plan back with what to change.",
5534
5543
  "approve": "Approve plan",
5535
- "requestChanges": "Request changes",
5536
5544
  "feedbackPlaceholder": "What should the planner change? Scope, phase order, missing work, an item that belongs elsewhere…",
5537
- "sendBack": "Send back to the planner"
5545
+ "sendBack": "Send back to the planner",
5546
+ "noDocument": "This plan was drafted before the platform rendered plans for review, so there is no document to navigate — the sections below are the plan.",
5547
+ "needsFeedback": "Add a comment or some feedback first — the planner re-plans from it.",
5548
+ "commentHint": "Click any part of the plan to comment on it."
5538
5549
  },
5539
5550
  "planning": {
5540
5551
  "title": "Plan the initiative",
@@ -299,7 +299,9 @@
299
299
  "focus": "Enfoque de la revisión",
300
300
  "focusPlaceholder": "p. ej., céntrate en los cambios de autenticación y el manejo de errores",
301
301
  "derivedTitle": "Revisar {ref}",
302
- "derivedTitleFallback": "Revisar la pull request"
302
+ "derivedTitleFallback": "Revisar la pull request",
303
+ "prNotFound": "No se encontró la pull request n.º {number} en el repositorio de este servicio. Comprueba el número, o vincula el servicio al repositorio en el que está esa pull request.",
304
+ "prRepoMismatch": "Esa pull request está en otro repositorio. Este servicio revisa {repo}, así que crea la tarea de revisión en el servicio vinculado al repositorio de la pull request."
303
305
  }
304
306
  },
305
307
  "recurring": {
@@ -944,6 +946,12 @@
944
946
  "risk": "Riesgo",
945
947
  "impact": "Impacto"
946
948
  },
949
+ "reviewTarget": {
950
+ "title": "Pull request en revisión",
951
+ "hint": "La pull request existente que revisa esta tarea.",
952
+ "prNumber": "PR n.º {number}",
953
+ "focus": "Enfoque: {focus}"
954
+ },
947
955
  "execution": {
948
956
  "title": "Ejecución",
949
957
  "hint": "La ejecución en vivo del pipeline: cada paso de agente, su progreso y el pull request resultante.",
@@ -1284,6 +1292,7 @@
1284
1292
  "editConclusionsPlaceholder": "Edita las conclusiones del agente; tus cambios se guardan cuando apruebas…",
1285
1293
  "noProseOutput": "Este agente no produjo salida en prosa.",
1286
1294
  "approveWithCorrections": "Aprobar con correcciones",
1295
+ "renderedOutputNote": "Esta salida es una representación de lo que produjo el paso, por lo que no se puede editar aquí. Solicita cambios para que se revise.",
1287
1296
  "reviewAndApprove": "Revisar y aprobar",
1288
1297
  "editHint": "Edita las conclusiones a la izquierda; tus cambios se guardan cuando apruebas.",
1289
1298
  "reviewHint": "Haz clic en cualquier bloque de la salida para comentarlo, o deja comentarios generales abajo.",
@@ -5351,9 +5360,11 @@
5351
5360
  "title": "Este plan te está esperando",
5352
5361
  "body": "El planificador redactó las fases y los elementos de abajo. Apruébalos para confirmar el plan y empezar el trabajo, o devuelve el plan indicando qué cambiar.",
5353
5362
  "approve": "Aprobar plan",
5354
- "requestChanges": "Solicitar cambios",
5355
5363
  "feedbackPlaceholder": "¿Qué debería cambiar el planificador? Alcance, orden de las fases, trabajo que falta, un elemento que va en otro sitio…",
5356
- "sendBack": "Devolver al planificador"
5364
+ "sendBack": "Devolver al planificador",
5365
+ "noDocument": "Este plan se redactó antes de que la plataforma generara planes para revisión, así que no hay documento que recorrer: las secciones de abajo son el plan.",
5366
+ "needsFeedback": "Añade primero un comentario o algún comentario general: el planificador replanifica a partir de ello.",
5367
+ "commentHint": "Haz clic en cualquier parte del plan para comentarla."
5357
5368
  },
5358
5369
  "planning": {
5359
5370
  "title": "Planificar la iniciativa",
@@ -299,7 +299,9 @@
299
299
  "focus": "Objet de la revue",
300
300
  "focusPlaceholder": "p. ex. concentrez-vous sur les changements d'authentification et la gestion des erreurs",
301
301
  "derivedTitle": "Examiner {ref}",
302
- "derivedTitleFallback": "Examiner la pull request"
302
+ "derivedTitleFallback": "Examiner la pull request",
303
+ "prNotFound": "La pull request n° {number} est introuvable dans le dépôt de ce service. Vérifiez le numéro, ou reliez le service au dépôt qui héberge cette pull request.",
304
+ "prRepoMismatch": "Cette pull request se trouve dans un autre dépôt. Ce service examine {repo} : créez la tâche de revue sous le service relié au dépôt de la pull request."
303
305
  }
304
306
  },
305
307
  "recurring": {
@@ -944,6 +946,12 @@
944
946
  "risk": "Risque",
945
947
  "impact": "Impact"
946
948
  },
949
+ "reviewTarget": {
950
+ "title": "Pull request en revue",
951
+ "hint": "La pull request existante que cette tâche examine.",
952
+ "prNumber": "PR n° {number}",
953
+ "focus": "Objet : {focus}"
954
+ },
947
955
  "execution": {
948
956
  "title": "Exécution",
949
957
  "hint": "L'exécution en direct du pipeline : chaque étape d'agent, sa progression et la pull request obtenue.",
@@ -1284,6 +1292,7 @@
1284
1292
  "editConclusionsPlaceholder": "Modifiez les conclusions de l'agent ; vos modifications sont enregistrées lorsque vous approuvez…",
1285
1293
  "noProseOutput": "Cet agent n'a produit aucune sortie en texte libre.",
1286
1294
  "approveWithCorrections": "Approuver avec corrections",
1295
+ "renderedOutputNote": "Cette sortie est un rendu de ce que l'étape a produit ; elle ne peut pas être modifiée ici. Demandez des modifications pour la faire réviser.",
1287
1296
  "reviewAndApprove": "Réviser et approuver",
1288
1297
  "editHint": "Modifiez les conclusions à gauche ; vos modifications sont enregistrées lorsque vous approuvez.",
1289
1298
  "reviewHint": "Cliquez sur n'importe quel bloc de la sortie pour le commenter, ou laissez un retour global ci-dessous.",
@@ -5351,9 +5360,11 @@
5351
5360
  "title": "Ce plan vous attend",
5352
5361
  "body": "Le planificateur a rédigé les phases et les éléments ci-dessous. Approuvez-les pour valider le plan et lancer le travail, ou renvoyez le plan en indiquant ce qu'il faut changer.",
5353
5362
  "approve": "Approuver le plan",
5354
- "requestChanges": "Demander des modifications",
5355
5363
  "feedbackPlaceholder": "Que doit changer le planificateur ? Périmètre, ordre des phases, travail manquant, un élément qui a sa place ailleurs…",
5356
- "sendBack": "Renvoyer au planificateur"
5364
+ "sendBack": "Renvoyer au planificateur",
5365
+ "noDocument": "Ce plan a été rédigé avant que la plateforme ne produise des plans à relire ; il n'y a donc pas de document à parcourir — les sections ci-dessous sont le plan.",
5366
+ "needsFeedback": "Ajoutez d'abord un commentaire ou un retour : le planificateur s'en sert pour replanifier.",
5367
+ "commentHint": "Cliquez sur une partie du plan pour la commenter."
5357
5368
  },
5358
5369
  "planning": {
5359
5370
  "title": "Planifier l'initiative",