@cat-factory/app 0.138.1 → 0.140.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 (47) hide show
  1. package/app/components/board/AddTaskModal.vue +12 -39
  2. package/app/components/brainstorm/BrainstormWindow.vue +338 -374
  3. package/app/components/clarity/ClarityReviewWindow.vue +323 -353
  4. package/app/components/consensus/ConsensusSessionWindow.vue +138 -150
  5. package/app/components/docs/DocInterviewWindow.vue +108 -136
  6. package/app/components/documents/ContextDocumentPicker.vue +89 -70
  7. package/app/components/documents/RepoContextDocPicker.vue +277 -0
  8. package/app/components/followUp/FollowUpWindow.vue +3 -5
  9. package/app/components/forkDecision/ForkDecisionWindow.vue +1 -3
  10. package/app/components/gates/GateResultView.vue +3 -5
  11. package/app/components/github/RepoTreeBrowser.vue +19 -10
  12. package/app/components/humanTest/HumanTestWindow.vue +3 -5
  13. package/app/components/initiative/InitiativePlanningWindow.vue +89 -112
  14. package/app/components/initiative/InitiativeTrackerWindow.vue +389 -424
  15. package/app/components/panels/GenericStructuredResultView.vue +3 -5
  16. package/app/components/panels/MergerResultView.vue +3 -5
  17. package/app/components/panels/ResultWindowShell.vue +1 -1
  18. package/app/components/panels/inspector/TaskRunSettings.vue +16 -23
  19. package/app/components/pipeline/PipelineBuilder.vue +10 -0
  20. package/app/components/pipeline/PipelinePicker.vue +120 -0
  21. package/app/components/pipeline/PipelinePreview.vue +49 -0
  22. package/app/components/prReview/PrReviewWindow.vue +181 -205
  23. package/app/components/ralph/RalphLoopResultView.vue +3 -5
  24. package/app/components/requirements/RequirementsReviewWindow.vue +512 -557
  25. package/app/components/spec/ServiceSpecWindow.vue +235 -266
  26. package/app/components/testing/TestReportWindow.vue +4 -6
  27. package/app/components/visualConfirm/VisualConfirmationWindow.vue +3 -5
  28. package/app/composables/api/github.ts +8 -0
  29. package/app/composables/useContextLinking.spec.ts +138 -0
  30. package/app/composables/useContextLinking.ts +120 -8
  31. package/app/composables/useCopyToClipboard.spec.ts +27 -0
  32. package/app/composables/useCopyToClipboard.ts +17 -1
  33. package/app/composables/useResultView.ts +10 -21
  34. package/app/stores/github.ts +24 -0
  35. package/app/stores/pipelines.ts +9 -0
  36. package/app/utils/pipeline.ts +25 -1
  37. package/i18n/locales/de.json +20 -0
  38. package/i18n/locales/en.json +20 -0
  39. package/i18n/locales/es.json +20 -0
  40. package/i18n/locales/fr.json +20 -0
  41. package/i18n/locales/he.json +20 -0
  42. package/i18n/locales/it.json +20 -0
  43. package/i18n/locales/ja.json +20 -0
  44. package/i18n/locales/pl.json +20 -0
  45. package/i18n/locales/tr.json +20 -0
  46. package/i18n/locales/uk.json +20 -0
  47. package/package.json +2 -2
@@ -18,11 +18,9 @@ const agents = useAgentsStore()
18
18
  const { t } = useI18n()
19
19
 
20
20
  // Shared seam contract (open/blockId/close). No `onOpen` loader: this window reads its data
21
- // straight off the execution step, so there's nothing to fetch on open. `manageEscape: false`
22
- // — `ResultWindowShell` owns Escape (and focus trap + scroll lock + stacking).
23
- const { open, blockId, instanceId, stepIndex, close } = useResultView('generic-structured', {
24
- manageEscape: false,
25
- })
21
+ // straight off the execution step, so there's nothing to fetch on open. `ResultWindowShell`
22
+ // owns Escape (and focus trap + scroll lock + stacking).
23
+ const { open, blockId, instanceId, stepIndex, close } = useResultView('generic-structured')
26
24
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
27
25
 
28
26
  const instance = computed(() =>
@@ -18,11 +18,9 @@ const agents = useAgentsStore()
18
18
  const { t, n } = useI18n()
19
19
 
20
20
  // Shared seam contract (open/blockId/close). No loader: the verdict is read straight off
21
- // the execution step. `manageEscape: false` — `ResultWindowShell` owns Escape (and focus
22
- // trap + scroll lock + stacking) via the shared overlay behaviour.
23
- const { open, blockId, instanceId, stepIndex, close } = useResultView('merger', {
24
- manageEscape: false,
25
- })
21
+ // the execution step. `ResultWindowShell` owns Escape (and focus trap + scroll lock +
22
+ // stacking) via the shared overlay behaviour.
23
+ const { open, blockId, instanceId, stepIndex, close } = useResultView('merger')
26
24
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
27
25
 
28
26
  const instance = computed(() =>
@@ -12,7 +12,7 @@
12
12
  // `useModalBehavior` (`@modular-vue/core`, the slice-5 overlay-host release): focus-trap
13
13
  // + focus-return, body-scroll lock, and a shared overlay STACK so the top overlay closes
14
14
  // first on Escape. A window becomes body-only markup wrapped in `<ResultWindowShell>`; it
15
- // keeps its `useResultView` seam but passes `manageEscape: false` (the shell owns Escape).
15
+ // keeps its `useResultView` seam (Escape lives in this shell now, not in `useResultView`).
16
16
  //
17
17
  // The pick-one SELECTION of which window is active stays exactly the slice-2
18
18
  // `resolveComponentRegistry` in `StepResultViewHost.vue` — this shell only owns the
@@ -142,20 +142,6 @@ const selectablePipelines = computed(() =>
142
142
  pipelineAllowedForManualStart(p, taskFrame.value, board.blocks),
143
143
  ),
144
144
  )
145
- const pipelineMenu = computed(() => [
146
- [
147
- {
148
- label: t('inspector.runSettings.noDefault'),
149
- icon: 'i-lucide-rotate-ccw',
150
- onSelect: () => setPipeline(''),
151
- },
152
- ...selectablePipelines.value.map((p) => ({
153
- label: p.name,
154
- icon: 'i-lucide-workflow',
155
- onSelect: () => setPipeline(p.id),
156
- })),
157
- ],
158
- ])
159
145
  function setPipeline(id: string) {
160
146
  board.updateBlock(props.block.id, { pipelineId: id })
161
147
  }
@@ -271,15 +257,22 @@ const technicalLabel = computed(() => {
271
257
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
272
258
  {{ t('inspector.runSettings.pipeline') }}
273
259
  </span>
274
- <UDropdownMenu :items="pipelineMenu">
275
- <UButton
276
- size="xs"
277
- variant="ghost"
278
- color="neutral"
279
- icon="i-lucide-workflow"
280
- trailing-icon="i-lucide-chevron-down"
281
- />
282
- </UDropdownMenu>
260
+ <PipelinePicker
261
+ :model-value="block.pipelineId ?? ''"
262
+ :options="selectablePipelines"
263
+ :none-label="t('inspector.runSettings.noDefault')"
264
+ @update:model-value="setPipeline"
265
+ >
266
+ <template #trigger>
267
+ <UButton
268
+ size="xs"
269
+ variant="ghost"
270
+ color="neutral"
271
+ icon="i-lucide-workflow"
272
+ trailing-icon="i-lucide-chevron-down"
273
+ />
274
+ </template>
275
+ </PipelinePicker>
283
276
  </div>
284
277
  <div v-if="selectedPipeline" class="flex items-center gap-1">
285
278
  <UBadge
@@ -310,6 +310,16 @@ async function clone(p: Pipeline) {
310
310
  class="mb-2"
311
311
  />
312
312
 
313
+ <!-- Description: the prose summary shown next to the step list in the pipeline pickers. -->
314
+ <UTextarea
315
+ v-model="pipelines.draftDescription"
316
+ :placeholder="t('pipeline.builder.descriptionPlaceholder')"
317
+ :rows="2"
318
+ autoresize
319
+ size="sm"
320
+ class="mb-2 w-full"
321
+ />
322
+
313
323
  <!-- Labels: organize the pipeline in the library (filter/search). -->
314
324
  <div class="mb-3 flex flex-wrap items-center gap-1.5">
315
325
  <UBadge
@@ -0,0 +1,120 @@
1
+ <script setup lang="ts">
2
+ // The rich pipeline picker used wherever a pipeline is chosen (add-task modal, inspector run
3
+ // settings). A master–detail popover: the left column lists the selectable pipelines (plus a
4
+ // "none / choose at run time" row), and hovering a row reveals that pipeline's full preview — its
5
+ // description + the ordered agent steps — in the right column, so a user sees exactly what a
6
+ // pipeline does before picking it. The trigger is customizable via the `#trigger` slot (the
7
+ // inspector uses a bare icon button; the modal a full-width labelled one).
8
+ import { computed, ref } from 'vue'
9
+ import type { Pipeline } from '~/types/domain'
10
+
11
+ const props = withDefaults(
12
+ defineProps<{
13
+ /** Selected pipeline id, or '' for the "none" option. */
14
+ modelValue: string
15
+ /** The pipelines offered (already filtered for the surface, e.g. manual-start allowed). */
16
+ options: Pipeline[]
17
+ /** Label for the "none" row (e.g. "Choose at run time" / "No default"). */
18
+ noneLabel: string
19
+ /** Extra classes for the default trigger button (e.g. full-width in the modal). */
20
+ triggerClass?: string
21
+ }>(),
22
+ { triggerClass: '' },
23
+ )
24
+
25
+ const emit = defineEmits<{ 'update:modelValue': [string] }>()
26
+ const { t } = useI18n()
27
+
28
+ const open = ref(false)
29
+ // The row currently hovered, driving the right-column preview. `undefined` ⇒ fall back to the
30
+ // selected pipeline; the sentinel '' means the "none" row is hovered (show the none hint).
31
+ const hoverId = ref<string | undefined>(undefined)
32
+
33
+ const selected = computed(() => props.options.find((p) => p.id === props.modelValue))
34
+ const triggerLabel = computed(() => selected.value?.name ?? props.noneLabel)
35
+
36
+ /** The pipeline the right pane previews: the hovered row, else the current selection. */
37
+ const previewPipeline = computed<Pipeline | null>(() => {
38
+ const id = hoverId.value ?? props.modelValue
39
+ return id ? (props.options.find((p) => p.id === id) ?? null) : null
40
+ })
41
+
42
+ function choose(id: string) {
43
+ emit('update:modelValue', id)
44
+ open.value = false
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <UPopover v-model:open="open" :content="{ align: 'start' }">
50
+ <slot name="trigger" :label="triggerLabel">
51
+ <UButton
52
+ color="neutral"
53
+ variant="subtle"
54
+ size="sm"
55
+ icon="i-lucide-workflow"
56
+ trailing-icon="i-lucide-chevron-down"
57
+ :class="triggerClass"
58
+ data-testid="pipeline-picker-trigger"
59
+ >
60
+ {{ triggerLabel }}
61
+ </UButton>
62
+ </slot>
63
+
64
+ <template #content>
65
+ <div
66
+ class="flex max-h-[24rem] w-[min(44rem,94vw)]"
67
+ data-testid="pipeline-picker-panel"
68
+ @mouseleave="hoverId = undefined"
69
+ >
70
+ <!-- left: selectable options -->
71
+ <ul class="w-1/2 shrink-0 overflow-y-auto border-e border-slate-800 p-1">
72
+ <li>
73
+ <button
74
+ type="button"
75
+ class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-start text-sm hover:bg-slate-800/60"
76
+ :class="modelValue ? 'text-slate-300' : 'text-slate-100'"
77
+ data-testid="pipeline-option-none"
78
+ @mouseenter="hoverId = ''"
79
+ @click="choose('')"
80
+ >
81
+ <UIcon name="i-lucide-rotate-ccw" class="h-4 w-4 shrink-0 text-slate-400" />
82
+ <span class="flex-1 truncate">{{ noneLabel }}</span>
83
+ <UIcon
84
+ v-if="!modelValue"
85
+ name="i-lucide-check"
86
+ class="h-4 w-4 shrink-0 text-primary-400"
87
+ />
88
+ </button>
89
+ </li>
90
+ <li v-for="p in options" :key="p.id">
91
+ <button
92
+ type="button"
93
+ class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-start text-sm hover:bg-slate-800/60"
94
+ :class="modelValue === p.id ? 'text-slate-100' : 'text-slate-300'"
95
+ :data-testid="`pipeline-option-${p.id}`"
96
+ @mouseenter="hoverId = p.id"
97
+ @click="choose(p.id)"
98
+ >
99
+ <UIcon name="i-lucide-workflow" class="h-4 w-4 shrink-0 text-slate-400" />
100
+ <span class="flex-1 truncate">{{ p.name }}</span>
101
+ <UIcon
102
+ v-if="modelValue === p.id"
103
+ name="i-lucide-check"
104
+ class="h-4 w-4 shrink-0 text-primary-400"
105
+ />
106
+ </button>
107
+ </li>
108
+ </ul>
109
+
110
+ <!-- right: preview of the hovered (or selected) pipeline -->
111
+ <div class="w-1/2 overflow-y-auto p-3">
112
+ <PipelinePreview v-if="previewPipeline" :pipeline="previewPipeline" />
113
+ <div v-else class="text-[12px] leading-snug text-slate-500">
114
+ {{ t('pipeline.picker.noneHint') }}
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </template>
119
+ </UPopover>
120
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ // A compact, read-only summary of a pipeline: its name, prose description (when authored), and the
3
+ // ordered list of enabled agent steps rendered as icon+label chips (a human-gated step is flagged).
4
+ // Shared by the pipeline pickers' hover preview so "what a pipeline consists of" is explained the
5
+ // same way everywhere. Resolves each step's display metadata through the single `agentKindMeta`
6
+ // path (via <AgentKindIcon>), so a system/custom kind can never blow up the renderer.
7
+ import { computed } from 'vue'
8
+ import type { Pipeline } from '~/types/domain'
9
+ import { pipelineDisplaySteps } from '~/utils/pipeline'
10
+
11
+ const props = defineProps<{ pipeline: Pipeline }>()
12
+ const { t } = useI18n()
13
+
14
+ const steps = computed(() => pipelineDisplaySteps(props.pipeline))
15
+ </script>
16
+
17
+ <template>
18
+ <div class="space-y-2" data-testid="pipeline-preview">
19
+ <div class="text-sm font-semibold text-slate-100">{{ pipeline.name }}</div>
20
+ <p
21
+ v-if="pipeline.description"
22
+ class="text-[12px] leading-snug text-slate-400"
23
+ data-testid="pipeline-preview-description"
24
+ >
25
+ {{ pipeline.description }}
26
+ </p>
27
+ <div>
28
+ <div class="mb-1 flex items-center gap-1 text-[10px] uppercase tracking-wide text-slate-500">
29
+ <UIcon name="i-lucide-workflow" class="h-3 w-3" />
30
+ {{ t('pipeline.preview.stepCount', { count: steps.length }, steps.length) }}
31
+ </div>
32
+ <ol class="flex flex-wrap items-center gap-1">
33
+ <li
34
+ v-for="(s, i) in steps"
35
+ :key="i"
36
+ class="inline-flex items-center gap-1 rounded bg-slate-800/70 px-1.5 py-0.5"
37
+ >
38
+ <AgentKindIcon :kind="s.kind" show-label icon-class="h-3.5 w-3.5" />
39
+ <UIcon
40
+ v-if="s.gated"
41
+ name="i-lucide-shield-check"
42
+ class="h-3 w-3 text-amber-400"
43
+ :title="t('pipeline.preview.gated')"
44
+ />
45
+ </li>
46
+ </ol>
47
+ </div>
48
+ </div>
49
+ </template>