@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.
- package/README.md +4 -1
- package/app/assets/css/main.css +2 -0
- package/app/assets/css/prose.css +135 -0
- package/app/components/board/AddTaskModal.vue +31 -1
- package/app/components/brainstorm/BrainstormWindow.vue +4 -37
- package/app/components/clarity/ClarityReviewWindow.vue +4 -37
- package/app/components/initiative/InitiativePlanReview.vue +351 -0
- package/app/components/initiative/InitiativeTrackerWindow.vue +12 -126
- package/app/components/panels/AgentStepDetail.vue +19 -122
- package/app/components/panels/inspector/TaskReviewTarget.vue +69 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +4 -37
- package/app/composables/useProseComments.ts +126 -0
- package/app/composables/useStepApproval.ts +29 -85
- package/app/composables/useStepProse.spec.ts +67 -0
- package/app/composables/useStepProse.ts +22 -10
- package/app/modular/nav-contributions.spec.ts +3 -13
- package/app/modular/panels/inspector.logic.spec.ts +7 -0
- package/app/modular/panels/inspector.logic.ts +5 -0
- package/app/modular/panels/inspector.ts +2 -0
- package/app/stores/execution.ts +9 -0
- package/app/utils/initiative.spec.ts +24 -0
- package/i18n/locales/de.json +14 -3
- package/i18n/locales/en.json +14 -3
- package/i18n/locales/es.json +14 -3
- package/i18n/locales/fr.json +14 -3
- package/i18n/locales/he.json +14 -3
- package/i18n/locales/it.json +14 -3
- package/i18n/locales/ja.json +14 -3
- package/i18n/locales/pl.json +14 -3
- package/i18n/locales/tr.json +14 -3
- package/i18n/locales/uk.json +14 -3
- package/package.json +2 -2
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import { ref, computed, nextTick
|
|
1
|
+
import { ref, computed, nextTick } from 'vue'
|
|
2
2
|
import type { PipelineStep } from '~/types/execution'
|
|
3
|
-
import {
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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>('
|
|
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
|
|
22
|
-
// every heading section of the prose.
|
|
23
|
-
const anchors = computed(() => [
|
|
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] ?? '
|
|
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 = '
|
|
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
|
|
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
|
-
/**
|
|
14
|
-
const
|
|
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,
|
package/app/stores/execution.ts
CHANGED
|
@@ -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
|
+
})
|
package/i18n/locales/de.json
CHANGED
|
@@ -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",
|
package/i18n/locales/en.json
CHANGED
|
@@ -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",
|
package/i18n/locales/es.json
CHANGED
|
@@ -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",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -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",
|