@cat-factory/app 0.6.0 → 0.7.3

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 (33) hide show
  1. package/LICENSE +21 -21
  2. package/app/components/board/ContextPicker.vue +367 -367
  3. package/app/components/gates/GateResultView.vue +90 -12
  4. package/app/components/layout/SideBar.vue +11 -0
  5. package/app/components/observability/StepMetricsBar.vue +102 -102
  6. package/app/components/observability/StepModelActivity.vue +49 -0
  7. package/app/components/panels/ObservabilityPanel.vue +1 -1
  8. package/app/components/panels/StepMetadataCard.vue +4 -16
  9. package/app/components/panels/StepRunMeta.vue +105 -0
  10. package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -178
  11. package/app/components/panels/inspector/TaskRunSettings.vue +77 -0
  12. package/app/components/recurring/RecurrenceEditor.vue +124 -124
  13. package/app/components/settings/IssueTrackerWritebackPanel.vue +103 -0
  14. package/app/components/testing/TestReportWindow.vue +17 -8
  15. package/app/composables/useBlockQueries.ts +154 -154
  16. package/app/composables/useContextLinking.ts +65 -65
  17. package/app/composables/useFrameResize.ts +54 -54
  18. package/app/pages/index.vue +2 -0
  19. package/app/stores/documents.ts +176 -176
  20. package/app/stores/services.ts +87 -87
  21. package/app/stores/tracker.ts +39 -27
  22. package/app/stores/ui.ts +12 -0
  23. package/app/types/documents.ts +104 -104
  24. package/app/types/domain.ts +5 -1
  25. package/app/types/execution.ts +18 -0
  26. package/app/types/github.ts +173 -173
  27. package/app/types/services.ts +27 -27
  28. package/app/types/tasks.ts +82 -82
  29. package/app/types/tracker.ts +27 -18
  30. package/app/utils/agentOutput.spec.ts +128 -128
  31. package/app/utils/agentOutput.ts +173 -173
  32. package/app/utils/observability.ts +52 -52
  33. package/package.json +6 -1
@@ -1,124 +1,124 @@
1
- <script setup lang="ts">
2
- // Edits a `Recurrence`: run every N hours, optionally constrained to a set of
3
- // weekdays and an hour-of-day window, in a chosen timezone. Used both when adding
4
- // a recurring pipeline (frame modal) and when editing one (inspector). Emits the
5
- // updated recurrence via v-model.
6
- import type { Recurrence } from '~/types/recurring'
7
-
8
- const props = defineProps<{ modelValue: Recurrence }>()
9
- const emit = defineEmits<{ 'update:modelValue': [Recurrence] }>()
10
-
11
- const WEEKDAYS = [
12
- { value: 0, label: 'Sun' },
13
- { value: 1, label: 'Mon' },
14
- { value: 2, label: 'Tue' },
15
- { value: 3, label: 'Wed' },
16
- { value: 4, label: 'Thu' },
17
- { value: 5, label: 'Fri' },
18
- { value: 6, label: 'Sat' },
19
- ]
20
-
21
- function patch(p: Partial<Recurrence>) {
22
- emit('update:modelValue', { ...props.modelValue, ...p })
23
- }
24
-
25
- function toggleDay(day: number) {
26
- const set = new Set(props.modelValue.weekdays)
27
- if (set.has(day)) set.delete(day)
28
- else set.add(day)
29
- patch({ weekdays: [...set].sort((a, b) => a - b) })
30
- }
31
-
32
- // The hour-window is "any hour" when both bounds are null. The checkbox toggles
33
- // between unconstrained and a default business-hours window.
34
- const windowEnabled = computed(
35
- () => props.modelValue.windowStartHour !== null || props.modelValue.windowEndHour !== null,
36
- )
37
- function toggleWindow(enabled: boolean) {
38
- if (enabled) patch({ windowStartHour: 9, windowEndHour: 17 })
39
- else patch({ windowStartHour: null, windowEndHour: null })
40
- }
41
-
42
- const hours = Array.from({ length: 24 }, (_, h) => ({
43
- value: h,
44
- label: `${String(h).padStart(2, '0')}:00`,
45
- }))
46
-
47
- // A small, common set of IANA zones plus whatever the schedule already uses.
48
- const TIMEZONES = [
49
- 'UTC',
50
- 'Europe/London',
51
- 'Europe/Helsinki',
52
- 'America/New_York',
53
- 'America/Los_Angeles',
54
- 'Asia/Tokyo',
55
- ]
56
- const timezoneOptions = computed(() =>
57
- Array.from(new Set([props.modelValue.timezone, ...TIMEZONES])),
58
- )
59
- </script>
60
-
61
- <template>
62
- <div class="space-y-3">
63
- <UFormField label="Run every">
64
- <div class="flex items-center gap-2">
65
- <UInput
66
- :model-value="modelValue.intervalHours"
67
- type="number"
68
- :min="1"
69
- class="w-24"
70
- @update:model-value="patch({ intervalHours: Math.max(1, Number($event) || 1) })"
71
- />
72
- <span class="text-xs text-slate-400">hours</span>
73
- </div>
74
- </UFormField>
75
-
76
- <UFormField label="Allowed days" help="Leave all off to run any day.">
77
- <div class="flex flex-wrap gap-1">
78
- <UButton
79
- v-for="d in WEEKDAYS"
80
- :key="d.value"
81
- size="xs"
82
- :color="modelValue.weekdays.includes(d.value) ? 'primary' : 'neutral'"
83
- :variant="modelValue.weekdays.includes(d.value) ? 'solid' : 'subtle'"
84
- @click="toggleDay(d.value)"
85
- >
86
- {{ d.label }}
87
- </UButton>
88
- </div>
89
- </UFormField>
90
-
91
- <UFormField>
92
- <UCheckbox
93
- :model-value="windowEnabled"
94
- label="Only within an hour-of-day window (e.g. business hours)"
95
- @update:model-value="toggleWindow(Boolean($event))"
96
- />
97
- </UFormField>
98
-
99
- <div v-if="windowEnabled" class="flex items-center gap-2">
100
- <USelect
101
- :model-value="modelValue.windowStartHour ?? 0"
102
- :items="hours"
103
- class="w-28"
104
- @update:model-value="patch({ windowStartHour: Number($event) })"
105
- />
106
- <span class="text-xs text-slate-400">to</span>
107
- <USelect
108
- :model-value="modelValue.windowEndHour ?? 24 % 24"
109
- :items="hours"
110
- class="w-28"
111
- @update:model-value="patch({ windowEndHour: Number($event) })"
112
- />
113
- </div>
114
-
115
- <UFormField label="Timezone">
116
- <USelect
117
- :model-value="modelValue.timezone"
118
- :items="timezoneOptions"
119
- class="w-full"
120
- @update:model-value="patch({ timezone: String($event) })"
121
- />
122
- </UFormField>
123
- </div>
124
- </template>
1
+ <script setup lang="ts">
2
+ // Edits a `Recurrence`: run every N hours, optionally constrained to a set of
3
+ // weekdays and an hour-of-day window, in a chosen timezone. Used both when adding
4
+ // a recurring pipeline (frame modal) and when editing one (inspector). Emits the
5
+ // updated recurrence via v-model.
6
+ import type { Recurrence } from '~/types/recurring'
7
+
8
+ const props = defineProps<{ modelValue: Recurrence }>()
9
+ const emit = defineEmits<{ 'update:modelValue': [Recurrence] }>()
10
+
11
+ const WEEKDAYS = [
12
+ { value: 0, label: 'Sun' },
13
+ { value: 1, label: 'Mon' },
14
+ { value: 2, label: 'Tue' },
15
+ { value: 3, label: 'Wed' },
16
+ { value: 4, label: 'Thu' },
17
+ { value: 5, label: 'Fri' },
18
+ { value: 6, label: 'Sat' },
19
+ ]
20
+
21
+ function patch(p: Partial<Recurrence>) {
22
+ emit('update:modelValue', { ...props.modelValue, ...p })
23
+ }
24
+
25
+ function toggleDay(day: number) {
26
+ const set = new Set(props.modelValue.weekdays)
27
+ if (set.has(day)) set.delete(day)
28
+ else set.add(day)
29
+ patch({ weekdays: [...set].sort((a, b) => a - b) })
30
+ }
31
+
32
+ // The hour-window is "any hour" when both bounds are null. The checkbox toggles
33
+ // between unconstrained and a default business-hours window.
34
+ const windowEnabled = computed(
35
+ () => props.modelValue.windowStartHour !== null || props.modelValue.windowEndHour !== null,
36
+ )
37
+ function toggleWindow(enabled: boolean) {
38
+ if (enabled) patch({ windowStartHour: 9, windowEndHour: 17 })
39
+ else patch({ windowStartHour: null, windowEndHour: null })
40
+ }
41
+
42
+ const hours = Array.from({ length: 24 }, (_, h) => ({
43
+ value: h,
44
+ label: `${String(h).padStart(2, '0')}:00`,
45
+ }))
46
+
47
+ // A small, common set of IANA zones plus whatever the schedule already uses.
48
+ const TIMEZONES = [
49
+ 'UTC',
50
+ 'Europe/London',
51
+ 'Europe/Helsinki',
52
+ 'America/New_York',
53
+ 'America/Los_Angeles',
54
+ 'Asia/Tokyo',
55
+ ]
56
+ const timezoneOptions = computed(() =>
57
+ Array.from(new Set([props.modelValue.timezone, ...TIMEZONES])),
58
+ )
59
+ </script>
60
+
61
+ <template>
62
+ <div class="space-y-3">
63
+ <UFormField label="Run every">
64
+ <div class="flex items-center gap-2">
65
+ <UInput
66
+ :model-value="modelValue.intervalHours"
67
+ type="number"
68
+ :min="1"
69
+ class="w-24"
70
+ @update:model-value="patch({ intervalHours: Math.max(1, Number($event) || 1) })"
71
+ />
72
+ <span class="text-xs text-slate-400">hours</span>
73
+ </div>
74
+ </UFormField>
75
+
76
+ <UFormField label="Allowed days" help="Leave all off to run any day.">
77
+ <div class="flex flex-wrap gap-1">
78
+ <UButton
79
+ v-for="d in WEEKDAYS"
80
+ :key="d.value"
81
+ size="xs"
82
+ :color="modelValue.weekdays.includes(d.value) ? 'primary' : 'neutral'"
83
+ :variant="modelValue.weekdays.includes(d.value) ? 'solid' : 'subtle'"
84
+ @click="toggleDay(d.value)"
85
+ >
86
+ {{ d.label }}
87
+ </UButton>
88
+ </div>
89
+ </UFormField>
90
+
91
+ <UFormField>
92
+ <UCheckbox
93
+ :model-value="windowEnabled"
94
+ label="Only within an hour-of-day window (e.g. business hours)"
95
+ @update:model-value="toggleWindow(Boolean($event))"
96
+ />
97
+ </UFormField>
98
+
99
+ <div v-if="windowEnabled" class="flex items-center gap-2">
100
+ <USelect
101
+ :model-value="modelValue.windowStartHour ?? 0"
102
+ :items="hours"
103
+ class="w-28"
104
+ @update:model-value="patch({ windowStartHour: Number($event) })"
105
+ />
106
+ <span class="text-xs text-slate-400">to</span>
107
+ <USelect
108
+ :model-value="modelValue.windowEndHour ?? 24 % 24"
109
+ :items="hours"
110
+ class="w-28"
111
+ @update:model-value="patch({ windowEndHour: Number($event) })"
112
+ />
113
+ </div>
114
+
115
+ <UFormField label="Timezone">
116
+ <USelect
117
+ :model-value="modelValue.timezone"
118
+ :items="timezoneOptions"
119
+ class="w-full"
120
+ @update:model-value="patch({ timezone: String($event) })"
121
+ />
122
+ </UFormField>
123
+ </div>
124
+ </template>
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ // Workspace settings: issue-tracker writeback. Two independent toggles that govern
3
+ // whether the engine writes back to a task's linked tracker issue(s) as its PR
4
+ // progresses — comment when the PR opens, and comment + close as resolved when it
5
+ // merges. Each is overridable per task in the inspector. Persisted on the workspace
6
+ // tracker settings (the selection + Jira project key are preserved on save).
7
+ import { ref, watch } from 'vue'
8
+
9
+ const ui = useUiStore()
10
+ const tracker = useTrackerStore()
11
+ const toast = useToast()
12
+
13
+ const open = computed({
14
+ get: () => ui.issueWritebackOpen,
15
+ set: (v: boolean) => (v ? ui.openIssueWriteback() : ui.closeIssueWriteback()),
16
+ })
17
+
18
+ const commentOnPrOpen = ref(false)
19
+ const resolveOnMerge = ref(false)
20
+ const saving = ref(false)
21
+
22
+ // Re-sync the local toggles from the store whenever the panel opens.
23
+ watch(open, (isOpen) => {
24
+ if (!isOpen) return
25
+ commentOnPrOpen.value = tracker.settings.writebackCommentOnPrOpen
26
+ resolveOnMerge.value = tracker.settings.writebackResolveOnMerge
27
+ })
28
+
29
+ async function save() {
30
+ saving.value = true
31
+ try {
32
+ // Preserve the tracker selection + Jira project key; only the writeback flags change.
33
+ await tracker.save({
34
+ tracker: tracker.settings.tracker,
35
+ jiraProjectKey: tracker.settings.jiraProjectKey,
36
+ writebackCommentOnPrOpen: commentOnPrOpen.value,
37
+ writebackResolveOnMerge: resolveOnMerge.value,
38
+ })
39
+ toast.add({ title: 'Writeback settings saved', icon: 'i-lucide-check', color: 'success' })
40
+ } catch (e) {
41
+ toast.add({
42
+ title: 'Could not save settings',
43
+ description: e instanceof Error ? e.message : String(e),
44
+ icon: 'i-lucide-triangle-alert',
45
+ color: 'error',
46
+ })
47
+ } finally {
48
+ saving.value = false
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <template>
54
+ <UModal v-model:open="open" title="Issue tracker writeback" :ui="{ content: 'max-w-lg' }">
55
+ <template #body>
56
+ <div class="space-y-4">
57
+ <p class="text-xs text-slate-400">
58
+ When a task is linked to a tracker issue (GitHub Issues or Jira), write back to it as the
59
+ task's pull request progresses. Each toggle is the workspace default and can be overridden
60
+ per task in the inspector. GitHub issues close natively; Jira issues transition to the
61
+ first status in their <span class="text-slate-300">Done</span> category.
62
+ </p>
63
+
64
+ <label
65
+ class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3"
66
+ >
67
+ <USwitch v-model="commentOnPrOpen" />
68
+ <span class="text-sm">
69
+ <span class="block text-slate-200">Comment when a PR opens</span>
70
+ <span class="block text-xs text-slate-500">
71
+ Post a comment on the linked issue with the new pull request's link.
72
+ </span>
73
+ </span>
74
+ </label>
75
+
76
+ <label
77
+ class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3"
78
+ >
79
+ <USwitch v-model="resolveOnMerge" />
80
+ <span class="text-sm">
81
+ <span class="block text-slate-200">Close as resolved when a PR merges</span>
82
+ <span class="block text-xs text-slate-500">
83
+ Comment that the PR merged, then close / resolve the linked issue.
84
+ </span>
85
+ </span>
86
+ </label>
87
+
88
+ <div class="flex justify-end">
89
+ <UButton
90
+ color="primary"
91
+ variant="soft"
92
+ size="sm"
93
+ icon="i-lucide-save"
94
+ :loading="saving"
95
+ @click="save"
96
+ >
97
+ Save
98
+ </UButton>
99
+ </div>
100
+ </div>
101
+ </template>
102
+ </UModal>
103
+ </template>
@@ -12,6 +12,7 @@
12
12
  // `spec/features/*.feature` files would need a spec endpoint (a future enhancement).
13
13
  import type { TestConcern, TestOutcome, TestReport } from '~/types/domain'
14
14
  import StepRestartControl from '~/components/panels/StepRestartControl.vue'
15
+ import StepRunMeta from '~/components/panels/StepRunMeta.vue'
15
16
 
16
17
  const board = useBoardStore()
17
18
  const execution = useExecutionStore()
@@ -21,9 +22,12 @@ const execution = useExecutionStore()
21
22
  const { open, blockId, instanceId, stepIndex, close } = useResultView('tester')
22
23
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
23
24
 
25
+ const instance = computed(() =>
26
+ instanceId.value === null ? null : (execution.getInstance(instanceId.value) ?? null),
27
+ )
24
28
  const step = computed(() => {
25
- if (instanceId.value === null || stepIndex.value === null) return null
26
- return execution.getInstance(instanceId.value)?.steps[stepIndex.value] ?? null
29
+ if (instance.value === null || stepIndex.value === null) return null
30
+ return instance.value.steps[stepIndex.value] ?? null
27
31
  })
28
32
  const report = computed<TestReport | null>(() => step.value?.test?.lastReport ?? null)
29
33
  const testState = computed(() => step.value?.test ?? null)
@@ -385,12 +389,17 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
385
389
  <p class="text-[12px] capitalize text-slate-300">{{ report.environment }}</p>
386
390
  </div>
387
391
 
388
- <div v-if="step?.model">
389
- <h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
390
- Model
391
- </h4>
392
- <p class="break-all text-[12px] text-slate-300">{{ step.model }}</p>
393
- </div>
392
+ <!-- Shared run metadata + embedded observability (model, run id, timing,
393
+ model-activity rollup) identical to the gate and agent step detail. -->
394
+ <StepRunMeta
395
+ v-if="step"
396
+ :step="step"
397
+ :instance-id="instanceId ?? undefined"
398
+ :step-number="stepIndex === null ? undefined : stepIndex + 1"
399
+ :total-steps="instance?.steps.length"
400
+ :run-failed="instance?.status === 'failed'"
401
+ :failure-at="instance?.failure?.occurredAt"
402
+ />
394
403
 
395
404
  <p class="mt-auto text-[10px] leading-relaxed text-slate-600">
396
405
  Scenarios are the areas the Tester chose to exercise (its spec acceptance scenarios).