@cat-factory/app 0.6.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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/app/app.config.ts +8 -0
  4. package/app/app.vue +11 -0
  5. package/app/assets/css/main.css +100 -0
  6. package/app/components/auth/AuthGate.vue +24 -0
  7. package/app/components/auth/LoginScreen.vue +143 -0
  8. package/app/components/auth/UserMenu.vue +39 -0
  9. package/app/components/board/AddTaskModal.vue +444 -0
  10. package/app/components/board/AgentFailureCard.vue +97 -0
  11. package/app/components/board/AgentStopButton.vue +61 -0
  12. package/app/components/board/BoardCanvas.vue +183 -0
  13. package/app/components/board/ContextPicker.vue +367 -0
  14. package/app/components/board/RecurringPipelineModal.vue +219 -0
  15. package/app/components/board/TaskDependencyEdges.vue +132 -0
  16. package/app/components/board/nodes/AgentChip.vue +59 -0
  17. package/app/components/board/nodes/BlockNode.vue +433 -0
  18. package/app/components/board/nodes/DecisionBadge.vue +27 -0
  19. package/app/components/board/nodes/DraggableTask.vue +48 -0
  20. package/app/components/board/nodes/ModuleFrame.vue +97 -0
  21. package/app/components/board/nodes/TaskCard.vue +359 -0
  22. package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
  23. package/app/components/bootstrap/BootstrapModal.vue +665 -0
  24. package/app/components/clarity/ClarityReviewWindow.vue +611 -0
  25. package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
  26. package/app/components/documents/DocumentImportModal.vue +161 -0
  27. package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
  28. package/app/components/documents/SpawnPreviewModal.vue +161 -0
  29. package/app/components/documents/TaskContextDocs.vue +83 -0
  30. package/app/components/focus/BlockFocusView.vue +171 -0
  31. package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
  32. package/app/components/gates/GateResultView.vue +282 -0
  33. package/app/components/github/AddServiceFromRepoModal.vue +354 -0
  34. package/app/components/github/GitHubConnect.vue +183 -0
  35. package/app/components/github/GitHubOnboarding.vue +45 -0
  36. package/app/components/github/GitHubPanel.vue +584 -0
  37. package/app/components/github/RepoTreeBrowser.vue +171 -0
  38. package/app/components/layout/AccountTeamSettings.vue +237 -0
  39. package/app/components/layout/BoardSwitcher.vue +280 -0
  40. package/app/components/layout/BoardToolbar.vue +156 -0
  41. package/app/components/layout/CommandBar.vue +336 -0
  42. package/app/components/layout/GitHubPatBanner.vue +73 -0
  43. package/app/components/layout/NotificationsInbox.vue +175 -0
  44. package/app/components/layout/SideBar.vue +314 -0
  45. package/app/components/layout/SpendWarningBanner.vue +107 -0
  46. package/app/components/observability/StepMetricsBar.vue +102 -0
  47. package/app/components/palettes/AgentPalette.vue +86 -0
  48. package/app/components/panels/AgentStepDetail.vue +737 -0
  49. package/app/components/panels/DecisionModal.vue +71 -0
  50. package/app/components/panels/InspectorPanel.vue +465 -0
  51. package/app/components/panels/ObservabilityPanel.vue +351 -0
  52. package/app/components/panels/StepMetadataCard.vue +253 -0
  53. package/app/components/panels/StepRestartControl.vue +90 -0
  54. package/app/components/panels/StepResultViewHost.vue +40 -0
  55. package/app/components/panels/StepTestReport.vue +84 -0
  56. package/app/components/panels/inspector/ContainerSummary.vue +74 -0
  57. package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
  58. package/app/components/panels/inspector/ServiceFragments.vue +82 -0
  59. package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
  60. package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
  61. package/app/components/panels/inspector/TaskDependencies.vue +70 -0
  62. package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
  63. package/app/components/panels/inspector/TaskExecution.vue +364 -0
  64. package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
  65. package/app/components/panels/inspector/TaskStructure.vue +96 -0
  66. package/app/components/pipeline/AgentKindIcon.vue +30 -0
  67. package/app/components/pipeline/IterationCapPrompt.vue +70 -0
  68. package/app/components/pipeline/PipelineBuilder.vue +817 -0
  69. package/app/components/pipeline/PipelineProgress.vue +484 -0
  70. package/app/components/providers/ApiKeysSection.vue +273 -0
  71. package/app/components/providers/PersonalCredentialModal.vue +128 -0
  72. package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
  73. package/app/components/providers/VendorCredentialsModal.vue +197 -0
  74. package/app/components/recurring/RecurrenceEditor.vue +124 -0
  75. package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
  76. package/app/components/settings/DatadogPanel.vue +213 -0
  77. package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
  78. package/app/components/settings/MergeThresholdsPanel.vue +378 -0
  79. package/app/components/settings/ModelDefaultsPanel.vue +250 -0
  80. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
  81. package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
  82. package/app/components/slack/SlackPanel.vue +299 -0
  83. package/app/components/tasks/TaskContextIssues.vue +88 -0
  84. package/app/components/tasks/TaskImportModal.vue +207 -0
  85. package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
  86. package/app/components/testing/TestReportWindow.vue +404 -0
  87. package/app/composables/api/accounts.ts +81 -0
  88. package/app/composables/api/auth.ts +45 -0
  89. package/app/composables/api/board.ts +101 -0
  90. package/app/composables/api/bootstrap.ts +62 -0
  91. package/app/composables/api/context.ts +25 -0
  92. package/app/composables/api/documents.ts +74 -0
  93. package/app/composables/api/execution.ts +127 -0
  94. package/app/composables/api/fragments.ts +71 -0
  95. package/app/composables/api/github.ts +131 -0
  96. package/app/composables/api/models.ts +127 -0
  97. package/app/composables/api/notifications.ts +23 -0
  98. package/app/composables/api/presets.ts +29 -0
  99. package/app/composables/api/recurring.ts +68 -0
  100. package/app/composables/api/releaseHealth.ts +43 -0
  101. package/app/composables/api/reviews.ts +146 -0
  102. package/app/composables/api/slack.ts +54 -0
  103. package/app/composables/api/tasks.ts +72 -0
  104. package/app/composables/api/workspaces.ts +36 -0
  105. package/app/composables/useApi.ts +89 -0
  106. package/app/composables/useBlockDrag.ts +90 -0
  107. package/app/composables/useBlockQueries.ts +154 -0
  108. package/app/composables/useBoardFlow.ts +11 -0
  109. package/app/composables/useContextLinking.ts +65 -0
  110. package/app/composables/useDepLabels.ts +26 -0
  111. package/app/composables/useFrameResize.ts +54 -0
  112. package/app/composables/useResultView.ts +48 -0
  113. package/app/composables/useReviewStage.ts +40 -0
  114. package/app/composables/useSemanticZoom.ts +31 -0
  115. package/app/composables/useStepApproval.ts +233 -0
  116. package/app/composables/useStepProse.ts +78 -0
  117. package/app/composables/useStepTimer.ts +63 -0
  118. package/app/composables/useTaskExpansion.ts +92 -0
  119. package/app/composables/useWorkspaceStream.ts +155 -0
  120. package/app/docs/architecture.md +31 -0
  121. package/app/pages/index.vue +141 -0
  122. package/app/stores/accounts.ts +152 -0
  123. package/app/stores/agentConfig.ts +35 -0
  124. package/app/stores/agentRuns.ts +122 -0
  125. package/app/stores/agents.ts +40 -0
  126. package/app/stores/apiKeys.ts +108 -0
  127. package/app/stores/auth.ts +166 -0
  128. package/app/stores/board.spec.ts +205 -0
  129. package/app/stores/board.ts +286 -0
  130. package/app/stores/bootstrap.ts +97 -0
  131. package/app/stores/clarity.ts +196 -0
  132. package/app/stores/consensus.ts +60 -0
  133. package/app/stores/documents.ts +176 -0
  134. package/app/stores/execution.ts +273 -0
  135. package/app/stores/fragmentLibrary.ts +147 -0
  136. package/app/stores/fragments.ts +40 -0
  137. package/app/stores/github.ts +305 -0
  138. package/app/stores/localModels.ts +51 -0
  139. package/app/stores/mergePresets.ts +58 -0
  140. package/app/stores/modelDefaults.ts +76 -0
  141. package/app/stores/models.ts +134 -0
  142. package/app/stores/notifications.ts +70 -0
  143. package/app/stores/observability.ts +144 -0
  144. package/app/stores/personalSubscriptions.ts +215 -0
  145. package/app/stores/pipelines.ts +327 -0
  146. package/app/stores/recurringPipelines.ts +112 -0
  147. package/app/stores/releaseHealth.ts +75 -0
  148. package/app/stores/requirements.spec.ts +94 -0
  149. package/app/stores/requirements.ts +208 -0
  150. package/app/stores/serviceFragmentDefaults.ts +29 -0
  151. package/app/stores/services.ts +87 -0
  152. package/app/stores/slack.ts +142 -0
  153. package/app/stores/taskExpansion.ts +36 -0
  154. package/app/stores/tasks.spec.ts +71 -0
  155. package/app/stores/tasks.ts +176 -0
  156. package/app/stores/tracker.ts +27 -0
  157. package/app/stores/ui.ts +434 -0
  158. package/app/stores/vendorCredentials.ts +54 -0
  159. package/app/stores/workspace.ts +215 -0
  160. package/app/stores/workspaceSettings.ts +36 -0
  161. package/app/types/accounts.ts +77 -0
  162. package/app/types/bootstrap.ts +83 -0
  163. package/app/types/clarity.ts +59 -0
  164. package/app/types/consensus.ts +91 -0
  165. package/app/types/documents.ts +104 -0
  166. package/app/types/domain.ts +495 -0
  167. package/app/types/execution.ts +383 -0
  168. package/app/types/fragments.ts +72 -0
  169. package/app/types/github.ts +173 -0
  170. package/app/types/localModels.ts +73 -0
  171. package/app/types/merge.ts +71 -0
  172. package/app/types/models.ts +157 -0
  173. package/app/types/notifications.ts +74 -0
  174. package/app/types/recurring.ts +69 -0
  175. package/app/types/releaseHealth.ts +31 -0
  176. package/app/types/requirements.ts +61 -0
  177. package/app/types/services.ts +27 -0
  178. package/app/types/slack.ts +57 -0
  179. package/app/types/tasks.ts +82 -0
  180. package/app/types/tracker.ts +18 -0
  181. package/app/utils/agentOutput.spec.ts +128 -0
  182. package/app/utils/agentOutput.ts +173 -0
  183. package/app/utils/catalog.spec.ts +112 -0
  184. package/app/utils/catalog.ts +455 -0
  185. package/app/utils/dnd.ts +29 -0
  186. package/app/utils/observability.ts +52 -0
  187. package/app/utils/pipelineRender.ts +151 -0
  188. package/nuxt.config.ts +55 -0
  189. package/package.json +45 -0
@@ -0,0 +1,611 @@
1
+ <script setup lang="ts">
2
+ // Clarity review window — the dedicated surface for the `clarity-review` gate step
3
+ // (opened via the universal result-view host). The human reacts to the reviewer's
4
+ // structured triage of a BUG REPORT (answer the relevant questions, dismiss the
5
+ // irrelevant), then asks to incorporate. Incorporation + the re-review run
6
+ // ASYNCHRONOUSLY in the durable driver: the window closes and the user returns to the
7
+ // board, and is summoned back (a notification) only if the re-review raises new
8
+ // findings or hits the iteration cap. The clarified bug report — not the original
9
+ // description — is what every downstream agent step (the bug investigator, the coder)
10
+ // consumes.
11
+ import { parseOutputOutline } from '~/utils/agentOutput'
12
+ import type {
13
+ ClarityReview,
14
+ ClarityReviewItem,
15
+ ReviewItemCategory,
16
+ ReviewItemSeverity,
17
+ ReviewItemStatus,
18
+ } from '~/types/clarity'
19
+
20
+ const board = useBoardStore()
21
+ const clarity = useClarityStore()
22
+ const toast = useToast()
23
+
24
+ // Draft replies, keyed by item id, so editing one item doesn't disturb others.
25
+ const drafts = ref<Record<string, string>>({})
26
+ // Freeform "do it differently" comment when redoing a merge the human was unhappy with.
27
+ const redoComment = ref('')
28
+ const showRedo = ref(false)
29
+
30
+ // The seam contract (open/blockId/close + Escape handling + load-on-open) lives in
31
+ // `useResultView`, so this window can't drift from the others. Declaring `onOpen` makes the
32
+ // review load on EVERY open regardless of navigation route: the host mounts this window
33
+ // fresh each open, so a non-immediate per-window watch used to leave it empty for whichever
34
+ // route (a pipeline step / "Review & approve") didn't warm the cache by selecting the block.
35
+ const { open, blockId, close } = useResultView('clarity-review', {
36
+ onOpen: (id) => {
37
+ drafts.value = {}
38
+ redoComment.value = ''
39
+ showRedo.value = false
40
+ void clarity.load(id)
41
+ },
42
+ })
43
+ const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
44
+ const review = computed<ClarityReview | null>(() =>
45
+ blockId.value ? clarity.reviewFor(blockId.value) : null,
46
+ )
47
+ const busy = computed(() => (blockId.value ? clarity.isReviewing(blockId.value) : false))
48
+ // True while the initial fetch of an existing review is in flight (opening the window),
49
+ // before the cache is populated — so we show a spinner instead of the empty state.
50
+ const loading = computed(() => (blockId.value ? clarity.isLoading(blockId.value) : false))
51
+ const reworking = computed(() => (review.value ? clarity.isIncorporating(review.value.id) : false))
52
+ const acting = ref(false)
53
+
54
+ const SEVERITY_RANK: Record<ReviewItemSeverity, number> = { high: 0, medium: 1, low: 2 }
55
+ const sortedItems = computed<ClarityReviewItem[]>(() => {
56
+ if (!review.value) return []
57
+ return [...review.value.items].sort(
58
+ (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity],
59
+ )
60
+ })
61
+
62
+ const openCount = computed(() => (review.value ? clarity.openCount(review.value) : 0))
63
+ const answeredCount = computed(() => (review.value ? clarity.answeredCount(review.value) : 0))
64
+ const status = computed(() => review.value?.status ?? null)
65
+ const merged = computed(() => status.value === 'merged')
66
+ const exceeded = computed(() => status.value === 'exceeded')
67
+ const incorporated = computed(() => status.value === 'incorporated')
68
+ // The async cycle runs in the driver in two stages — folding the answers (`incorporating`)
69
+ // then re-reviewing the document (`reviewing`). The window normally closes the moment
70
+ // incorporation is requested; these states only show if it's later re-opened mid-cycle.
71
+ const incorporating = computed(() => status.value === 'incorporating')
72
+ const reReviewing = computed(() => status.value === 'reviewing')
73
+ const working = computed(() => incorporating.value || reReviewing.value)
74
+ // No edits while the bug report is clarified or a cycle is running in the background.
75
+ const frozen = computed(() => incorporated.value || working.value)
76
+ const canIncorporate = computed(() => !!review.value && clarity.canIncorporate(review.value))
77
+ const canProceed = computed(() => !!review.value && clarity.canProceed(review.value))
78
+ const iteration = computed(() => review.value?.iteration ?? 1)
79
+ const maxIterations = computed(() => review.value?.maxIterations ?? 1)
80
+
81
+ // The clarified bug report rendered as collapsible markdown (same reader the prose
82
+ // review window uses), shown once the companion has produced one.
83
+ const outline = computed(() =>
84
+ review.value?.clarifiedReport ? parseOutputOutline(review.value.clarifiedReport) : null,
85
+ )
86
+ const collapsed = ref<Record<string, boolean>>({})
87
+ function toggle(id: string) {
88
+ collapsed.value = { ...collapsed.value, [id]: !collapsed.value[id] }
89
+ }
90
+
91
+ const SEVERITY_COLOR = {
92
+ high: 'error',
93
+ medium: 'warning',
94
+ low: 'neutral',
95
+ } as const satisfies Record<ReviewItemSeverity, string>
96
+ const CATEGORY_ICON: Record<ReviewItemCategory, string> = {
97
+ gap: 'i-lucide-puzzle',
98
+ clarification: 'i-lucide-help-circle',
99
+ assumption: 'i-lucide-lightbulb',
100
+ risk: 'i-lucide-shield-alert',
101
+ question: 'i-lucide-message-circle-question',
102
+ }
103
+ const STATUS_COLOR = {
104
+ open: 'warning',
105
+ answered: 'info',
106
+ resolved: 'success',
107
+ dismissed: 'neutral',
108
+ } as const satisfies Record<ReviewItemStatus, string>
109
+
110
+ function notifyError(title: string, e: unknown) {
111
+ toast.add({
112
+ title,
113
+ description: e instanceof Error ? e.message : String(e),
114
+ icon: 'i-lucide-triangle-alert',
115
+ color: 'error',
116
+ })
117
+ }
118
+
119
+ async function submitReply(item: ClarityReviewItem) {
120
+ if (!review.value) return
121
+ const text = (drafts.value[item.id] ?? '').trim()
122
+ if (!text) return
123
+ try {
124
+ await clarity.reply(review.value, item.id, text)
125
+ drafts.value = { ...drafts.value, [item.id]: '' }
126
+ } catch (e) {
127
+ notifyError('Could not save the answer', e)
128
+ }
129
+ }
130
+
131
+ async function setStatus(item: ClarityReviewItem, itemStatus: ReviewItemStatus) {
132
+ if (!review.value) return
133
+ try {
134
+ await clarity.setItemStatus(review.value, item.id, itemStatus)
135
+ } catch (e) {
136
+ notifyError('Could not update the finding', e)
137
+ }
138
+ }
139
+
140
+ async function incorporate(feedback?: string) {
141
+ if (!review.value || !blockId.value) return
142
+ try {
143
+ await clarity.incorporate(review.value, feedback)
144
+ } catch (e) {
145
+ notifyError('Could not incorporate the answers', e)
146
+ return
147
+ }
148
+ redoComment.value = ''
149
+ showRedo.value = false
150
+ // The fold + re-review now run in the durable driver. Hand the user back to the board;
151
+ // a notification calls them back only if the re-review needs more input.
152
+ toast.add({
153
+ title: 'Clarifying the bug report in the background',
154
+ description: "You're back on the board — we'll notify you only if more input is needed.",
155
+ icon: 'i-lucide-wand-sparkles',
156
+ })
157
+ close()
158
+ }
159
+
160
+ async function reReview() {
161
+ if (!blockId.value) return
162
+ try {
163
+ const updated = await clarity.reReview(blockId.value)
164
+ toast.add({
165
+ title:
166
+ updated.status === 'incorporated'
167
+ ? 'Reviewer is satisfied — continuing the pipeline'
168
+ : updated.status === 'exceeded'
169
+ ? 'Iteration limit reached — choose how to proceed'
170
+ : `${clarity.openCount(updated)} new finding(s) to react to`,
171
+ icon: 'i-lucide-sparkles',
172
+ })
173
+ } catch (e) {
174
+ notifyError('Could not re-review the bug report', e)
175
+ }
176
+ }
177
+
178
+ async function proceed() {
179
+ if (!blockId.value) return
180
+ acting.value = true
181
+ try {
182
+ await clarity.proceed(blockId.value)
183
+ toast.add({ title: 'Proceeding to the next phase', icon: 'i-lucide-arrow-right' })
184
+ } catch (e) {
185
+ notifyError('Could not proceed', e)
186
+ } finally {
187
+ acting.value = false
188
+ }
189
+ }
190
+
191
+ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset') {
192
+ if (!blockId.value) return
193
+ acting.value = true
194
+ try {
195
+ await clarity.resolveExceeded(blockId.value, choice)
196
+ if (choice === 'stop-reset') {
197
+ toast.add({ title: 'Task reset — edit the bug report and resubmit', icon: 'i-lucide-undo' })
198
+ close()
199
+ } else if (choice === 'proceed') {
200
+ toast.add({ title: 'Proceeding to the next phase', icon: 'i-lucide-arrow-right' })
201
+ } else {
202
+ toast.add({ title: 'One more review round granted', icon: 'i-lucide-rotate-cw' })
203
+ }
204
+ } catch (e) {
205
+ notifyError('Could not resolve the review', e)
206
+ } finally {
207
+ acting.value = false
208
+ }
209
+ }
210
+ </script>
211
+
212
+ <template>
213
+ <Teleport to="body">
214
+ <div
215
+ v-if="open"
216
+ class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
217
+ @click.self="close"
218
+ >
219
+ <div
220
+ class="flex h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
221
+ >
222
+ <!-- header -->
223
+ <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
224
+ <div
225
+ class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-indigo-500/15"
226
+ >
227
+ <UIcon name="i-lucide-bug" class="h-5 w-5 text-indigo-300" />
228
+ </div>
229
+ <div class="min-w-0">
230
+ <h1 class="truncate text-base font-semibold text-white">Bug-report triage</h1>
231
+ <p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
232
+ </div>
233
+ <div class="ml-auto flex items-center gap-1.5">
234
+ <UBadge v-if="review" color="neutral" variant="subtle" size="sm">
235
+ Iteration {{ iteration }} / {{ maxIterations }}
236
+ </UBadge>
237
+ <UButton icon="i-lucide-x" color="neutral" variant="ghost" size="sm" @click="close" />
238
+ </div>
239
+ </header>
240
+
241
+ <div class="flex min-h-0 flex-1">
242
+ <!-- main column -->
243
+ <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
244
+ <p class="mb-4 text-sm text-slate-400">
245
+ An AI reviewer triaged this {{ block?.level ?? 'item' }}’s bug report for fixability —
246
+ its description plus any linked context — and raised the questions below.
247
+ <span class="text-slate-300">Answer</span> the relevant ones and
248
+ <span class="text-slate-300">dismiss</span> the irrelevant, then incorporate them; the
249
+ reviewer re-reviews until the bug report is clear enough to fix.
250
+ </p>
251
+
252
+ <!-- empty state — the reviewer runs automatically as the first pipeline
253
+ gate step, so there's nothing to do here until then -->
254
+ <div
255
+ v-if="!review && !busy && !loading"
256
+ class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
257
+ >
258
+ No review yet. The reviewer runs automatically as the first step when this task's
259
+ pipeline starts.
260
+ </div>
261
+
262
+ <!-- working state (initial fetch on open, or a reviewer pass running) -->
263
+ <div
264
+ v-else-if="(busy || loading) && !review"
265
+ class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
266
+ >
267
+ <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
268
+ {{ loading && !busy ? 'Loading the review…' : 'Triaging the bug report…' }}
269
+ </div>
270
+
271
+ <template v-else-if="review">
272
+ <!-- converged: reviewer satisfied -->
273
+ <div
274
+ v-if="incorporated"
275
+ class="mb-4 flex items-center gap-2 rounded-lg border border-emerald-900/60 bg-emerald-950/30 p-4 text-sm text-emerald-300"
276
+ >
277
+ <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
278
+ The bug report is clarified. The report below is what every downstream agent step
279
+ uses.
280
+ </div>
281
+
282
+ <!-- iteration cap hit -->
283
+ <IterationCapPrompt
284
+ v-else-if="exceeded"
285
+ class="mb-4"
286
+ :heading="`Reached the ${maxIterations}-iteration limit with findings still open.`"
287
+ detail="Do one more review round, proceed to the next phase with the last clarified bug report anyway, or stop and reset the task so you can rework the bug report and resubmit."
288
+ :loading="acting"
289
+ @resolve="resolveExceeded"
290
+ />
291
+
292
+ <!-- working: the async cycle is running in the driver. Two distinct stages so
293
+ the human can see which of the two LLM calls is currently in progress. -->
294
+ <div
295
+ v-else-if="working"
296
+ class="mb-4 flex items-center gap-2 rounded-lg border border-indigo-900/60 bg-indigo-950/30 p-4 text-sm text-indigo-200"
297
+ >
298
+ <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
299
+ <span v-if="incorporating">
300
+ Incorporating your answers into a clarified bug report… You can close this — we’ll
301
+ notify you only if more input is needed.
302
+ </span>
303
+ <span v-else>
304
+ Re-reviewing the updated bug report… You can close this — we’ll notify you only if
305
+ more input is needed.
306
+ </span>
307
+ </div>
308
+
309
+ <!-- findings to react to -->
310
+ <div v-if="review.items.length" class="flex flex-col gap-3">
311
+ <div
312
+ v-for="item in sortedItems"
313
+ :key="item.id"
314
+ class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
315
+ :class="{ 'opacity-60': item.status === 'dismissed' }"
316
+ >
317
+ <div class="flex items-start gap-2">
318
+ <UIcon
319
+ :name="CATEGORY_ICON[item.category]"
320
+ class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
321
+ />
322
+ <div class="min-w-0 flex-1">
323
+ <div class="flex flex-wrap items-center gap-1.5">
324
+ <span class="text-sm font-medium text-white">{{ item.title }}</span>
325
+ <UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
326
+ {{ item.severity }}
327
+ </UBadge>
328
+ <UBadge size="xs" variant="outline" color="neutral">
329
+ {{ item.category }}
330
+ </UBadge>
331
+ <UBadge
332
+ size="xs"
333
+ variant="soft"
334
+ :color="STATUS_COLOR[item.status]"
335
+ class="ml-auto"
336
+ >
337
+ {{ item.status }}
338
+ </UBadge>
339
+ </div>
340
+ <p class="mt-1 whitespace-pre-line text-sm text-slate-400">
341
+ {{ item.detail }}
342
+ </p>
343
+
344
+ <!-- recorded answer -->
345
+ <div
346
+ v-if="item.reply"
347
+ class="mt-2 rounded-md border-l-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
348
+ >
349
+ <span class="text-[10px] uppercase tracking-wide text-slate-500">
350
+ Answer
351
+ </span>
352
+ <p class="whitespace-pre-line">{{ item.reply }}</p>
353
+ </div>
354
+
355
+ <!-- react: answer (relevant) or dismiss (irrelevant). Disabled once the
356
+ bug report is clarified / awaiting a higher-level decision. -->
357
+ <template v-if="item.status === 'open' || item.status === 'answered'">
358
+ <UTextarea
359
+ v-model="drafts[item.id]"
360
+ :rows="2"
361
+ autoresize
362
+ size="sm"
363
+ class="mt-2 w-full"
364
+ :placeholder="item.reply ? 'Refine your answer…' : 'Answer this finding…'"
365
+ :disabled="frozen"
366
+ />
367
+ <div class="mt-2 flex flex-wrap items-center gap-2">
368
+ <UButton
369
+ color="primary"
370
+ variant="soft"
371
+ size="xs"
372
+ icon="i-lucide-corner-down-left"
373
+ :disabled="!(drafts[item.id] ?? '').trim() || frozen"
374
+ @click="submitReply(item)"
375
+ >
376
+ Save answer
377
+ </UButton>
378
+ <UButton
379
+ color="neutral"
380
+ variant="ghost"
381
+ size="xs"
382
+ icon="i-lucide-x"
383
+ :disabled="frozen"
384
+ @click="setStatus(item, 'dismissed')"
385
+ >
386
+ Dismiss as irrelevant
387
+ </UButton>
388
+ </div>
389
+ </template>
390
+
391
+ <!-- reopen a dismissed finding -->
392
+ <div v-else-if="item.status === 'dismissed'" class="mt-2">
393
+ <UButton
394
+ color="neutral"
395
+ variant="ghost"
396
+ size="xs"
397
+ icon="i-lucide-rotate-ccw"
398
+ :disabled="frozen"
399
+ @click="setStatus(item, 'open')"
400
+ >
401
+ Reopen
402
+ </UButton>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ </div>
407
+ </div>
408
+
409
+ <!-- clarified document: the standard-format bug report -->
410
+ <section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
411
+ <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
412
+ <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
413
+ <span class="font-semibold uppercase tracking-wide">
414
+ {{ incorporated ? 'Clarified bug report' : 'Clarified bug report (draft)' }}
415
+ </span>
416
+ </div>
417
+ <div v-for="s in outline.sections" :key="s.id" class="mb-2">
418
+ <button
419
+ v-if="s.title"
420
+ class="group flex w-full items-center gap-2 text-left"
421
+ @click="toggle(s.id)"
422
+ >
423
+ <UIcon
424
+ name="i-lucide-chevron-right"
425
+ class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
426
+ :class="collapsed[s.id] ? '' : 'rotate-90'"
427
+ />
428
+ <span
429
+ class="font-semibold text-white"
430
+ :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
431
+ v-html="s.titleHtml"
432
+ />
433
+ </button>
434
+ <div
435
+ v-show="!s.title || !collapsed[s.id]"
436
+ class="reader-prose mt-1 pl-5.5 text-[13px] leading-relaxed text-slate-300"
437
+ v-html="s.bodyHtml"
438
+ />
439
+ </div>
440
+ </section>
441
+ </template>
442
+ </div>
443
+
444
+ <!-- right action rail -->
445
+ <aside class="hidden w-72 shrink-0 flex-col border-l border-slate-800 lg:flex">
446
+ <div class="flex flex-col gap-4 px-4 py-5">
447
+ <div v-if="review" class="space-y-2 text-xs text-slate-400">
448
+ <div class="flex items-center justify-between">
449
+ <span>Findings</span>
450
+ <span class="text-slate-300">{{ review.items.length }}</span>
451
+ </div>
452
+ <div class="flex items-center justify-between">
453
+ <span>Open</span>
454
+ <span class="text-slate-300">{{ openCount }}</span>
455
+ </div>
456
+ <div class="flex items-center justify-between">
457
+ <span>Answered</span>
458
+ <span class="text-slate-300">{{ answeredCount }}</span>
459
+ </div>
460
+ <div v-if="review.model" class="flex items-center justify-between">
461
+ <span>Model</span>
462
+ <span class="truncate pl-2 text-slate-500">{{ review.model }}</span>
463
+ </div>
464
+ </div>
465
+
466
+ <!-- action: ready (answer → incorporate / proceed) -->
467
+ <div
468
+ v-if="review && status === 'ready'"
469
+ class="space-y-2 border-t border-slate-800 pt-4"
470
+ >
471
+ <UButton
472
+ v-if="canProceed"
473
+ color="primary"
474
+ size="sm"
475
+ block
476
+ icon="i-lucide-arrow-right"
477
+ :loading="acting"
478
+ @click="proceed"
479
+ >
480
+ Proceed (nothing to incorporate)
481
+ </UButton>
482
+ <UButton
483
+ v-else
484
+ color="primary"
485
+ size="sm"
486
+ block
487
+ icon="i-lucide-wand-sparkles"
488
+ :loading="reworking"
489
+ :disabled="!canIncorporate"
490
+ @click="incorporate()"
491
+ >
492
+ Incorporate answers
493
+ </UButton>
494
+ <p class="text-[11px] leading-relaxed text-slate-500">
495
+ <template v-if="canProceed">
496
+ Every finding is dismissed — proceed to the next phase without reworking.
497
+ </template>
498
+ <template v-else-if="canIncorporate">
499
+ Folds your answers into one clarified bug report, then re-reviews it
500
+ automatically.
501
+ </template>
502
+ <template v-else> Answer or dismiss every finding to continue. </template>
503
+ </p>
504
+ </div>
505
+
506
+ <!-- action: merged (inspect → re-review / redo) -->
507
+ <div v-if="review && merged" class="space-y-2 border-t border-slate-800 pt-4">
508
+ <UButton
509
+ color="primary"
510
+ size="sm"
511
+ block
512
+ icon="i-lucide-sparkles"
513
+ :loading="busy"
514
+ @click="reReview"
515
+ >
516
+ {{ busy ? 'Re-reviewing…' : 'Looks good — re-review' }}
517
+ </UButton>
518
+ <UButton
519
+ color="neutral"
520
+ variant="soft"
521
+ size="sm"
522
+ block
523
+ icon="i-lucide-pencil"
524
+ @click="showRedo = !showRedo"
525
+ >
526
+ Redo incorporation
527
+ </UButton>
528
+ <div v-if="showRedo" class="space-y-2">
529
+ <UTextarea
530
+ v-model="redoComment"
531
+ :rows="3"
532
+ autoresize
533
+ size="sm"
534
+ class="w-full"
535
+ placeholder="What should the merge do differently?"
536
+ />
537
+ <UButton
538
+ color="primary"
539
+ variant="soft"
540
+ size="xs"
541
+ block
542
+ icon="i-lucide-wand-sparkles"
543
+ :loading="reworking"
544
+ :disabled="!redoComment.trim()"
545
+ @click="incorporate(redoComment.trim())"
546
+ >
547
+ Redo with this direction
548
+ </UButton>
549
+ </div>
550
+ <p class="text-[11px] leading-relaxed text-slate-500">
551
+ Re-review runs the reviewer against this report. If you’re unhappy with how it was
552
+ merged, redo it with a comment instead.
553
+ </p>
554
+ </div>
555
+
556
+ <div
557
+ v-if="review && incorporated"
558
+ class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
559
+ >
560
+ Bug report clarified — the pipeline is continuing with the report on the left.
561
+ </div>
562
+ </div>
563
+ </aside>
564
+ </div>
565
+ </div>
566
+ </div>
567
+ </Teleport>
568
+ </template>
569
+
570
+ <style scoped>
571
+ .pl-5\.5 {
572
+ padding-left: 1.375rem;
573
+ }
574
+ /* Minimal CommonMark styling for the clarified bug-report reader (mirrors the
575
+ prose review window's reader-prose). */
576
+ .reader-prose :deep(p) {
577
+ margin: 0.4rem 0;
578
+ }
579
+ .reader-prose :deep(ul),
580
+ .reader-prose :deep(ol) {
581
+ margin: 0.4rem 0;
582
+ padding-left: 1.25rem;
583
+ list-style: revert;
584
+ }
585
+ .reader-prose :deep(li) {
586
+ margin: 0.2rem 0;
587
+ }
588
+ .reader-prose :deep(strong) {
589
+ color: rgb(226 232 240);
590
+ font-weight: 600;
591
+ }
592
+ .reader-prose :deep(code) {
593
+ border-radius: 0.25rem;
594
+ background: rgb(2 6 23 / 0.6);
595
+ padding: 0.05rem 0.3rem;
596
+ font-size: 0.85em;
597
+ }
598
+ .reader-prose :deep(pre) {
599
+ margin: 0.5rem 0;
600
+ overflow-x: auto;
601
+ border-radius: 0.5rem;
602
+ background: rgb(2 6 23 / 0.6);
603
+ padding: 0.75rem;
604
+ }
605
+ .reader-prose :deep(blockquote) {
606
+ margin: 0.5rem 0;
607
+ border-left: 2px solid rgb(51 65 85);
608
+ padding-left: 0.75rem;
609
+ color: rgb(148 163 184);
610
+ }
611
+ </style>