@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,6 +18,7 @@ import type {
18
18
  PrReviewSeverity,
19
19
  PrReviewStepState,
20
20
  } from '~/types/execution'
21
+ import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
21
22
 
22
23
  const execution = useExecutionStore()
23
24
  const board = useBoardStore()
@@ -122,219 +123,194 @@ async function onResolve(action: PrReviewResolution): Promise<void> {
122
123
  </script>
123
124
 
124
125
  <template>
125
- <Teleport to="body">
126
- <div
127
- v-if="open"
128
- data-testid="pr-review-window"
129
- class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
130
- @click.self="close"
131
- >
132
- <div
133
- class="m-4 flex w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
134
- role="dialog"
135
- aria-modal="true"
126
+ <ResultWindowShell
127
+ :open="open"
128
+ icon="i-lucide-clipboard-check"
129
+ icon-class="bg-indigo-500/15 text-indigo-300"
130
+ :title="block ? t('prReview.titleWithBlock', { title: block.title }) : t('prReview.title')"
131
+ :subtitle="t('prReview.subtitle')"
132
+ width="3xl"
133
+ testid="pr-review-window"
134
+ @close="close"
135
+ >
136
+ <template v-if="state?.prUrl" #header-extras>
137
+ <a
138
+ :href="state.prUrl"
139
+ target="_blank"
140
+ rel="noopener"
141
+ class="rounded-md px-2 py-1 text-[11px] text-indigo-300 hover:bg-slate-800"
136
142
  >
137
- <!-- Header -->
138
- <header class="flex items-center gap-3 border-b border-slate-800 px-5 py-3">
139
- <span
140
- class="flex h-8 w-8 items-center justify-center rounded-lg bg-indigo-500/15 text-indigo-300"
141
- >
142
- <UIcon name="i-lucide-clipboard-check" class="h-4 w-4" />
143
- </span>
144
- <div class="min-w-0 flex-1">
145
- <h2 class="truncate text-sm font-semibold text-slate-100">
146
- {{
147
- block ? t('prReview.titleWithBlock', { title: block.title }) : t('prReview.title')
148
- }}
149
- </h2>
150
- <p class="truncate text-[11px] text-slate-400">{{ t('prReview.subtitle') }}</p>
151
- </div>
152
- <a
153
- v-if="state?.prUrl"
154
- :href="state.prUrl"
155
- target="_blank"
156
- rel="noopener"
157
- class="rounded-md px-2 py-1 text-[11px] text-indigo-300 hover:bg-slate-800"
158
- >
159
- {{ t('prReview.openPr') }}
160
- </a>
161
- <button
162
- class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
163
- @click="close"
164
- >
165
- <UIcon name="i-lucide-x" class="h-4 w-4" />
166
- </button>
167
- </header>
143
+ {{ t('prReview.openPr') }}
144
+ </a>
145
+ </template>
168
146
 
169
- <div class="min-h-0 flex-1 overflow-y-auto px-5 py-4">
170
- <!-- Reviewing: the read-only reviewer is still working. -->
171
- <div
172
- v-if="status === 'reviewing'"
173
- class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
174
- >
175
- <UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
176
- <p class="text-sm">{{ t('prReview.reviewing.title') }}</p>
177
- <p class="max-w-sm text-[11px] text-slate-500">{{ t('prReview.reviewing.hint') }}</p>
178
- </div>
147
+ <div class="min-h-0 flex-1 overflow-y-auto px-5 py-4">
148
+ <!-- Reviewing: the read-only reviewer is still working. -->
149
+ <div
150
+ v-if="status === 'reviewing'"
151
+ class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
152
+ >
153
+ <UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
154
+ <p class="text-sm">{{ t('prReview.reviewing.title') }}</p>
155
+ <p class="max-w-sm text-[11px] text-slate-500">{{ t('prReview.reviewing.hint') }}</p>
156
+ </div>
179
157
 
180
- <!-- A resolution is executing: the Fixer is committing / comments are being posted. -->
181
- <div
182
- v-else-if="working"
183
- data-testid="pr-review-working"
184
- class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
185
- >
186
- <UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
187
- <p class="text-sm">
188
- {{ status === 'fixing' ? t('prReview.fixing.title') : t('prReview.posting.title') }}
189
- </p>
190
- <p class="max-w-sm text-[11px] text-slate-500">
191
- {{ status === 'fixing' ? t('prReview.fixing.hint') : t('prReview.posting.hint') }}
192
- </p>
193
- </div>
158
+ <!-- A resolution is executing: the Fixer is committing / comments are being posted. -->
159
+ <div
160
+ v-else-if="working"
161
+ data-testid="pr-review-working"
162
+ class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
163
+ >
164
+ <UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
165
+ <p class="text-sm">
166
+ {{ status === 'fixing' ? t('prReview.fixing.title') : t('prReview.posting.title') }}
167
+ </p>
168
+ <p class="max-w-sm text-[11px] text-slate-500">
169
+ {{ status === 'fixing' ? t('prReview.fixing.hint') : t('prReview.posting.hint') }}
170
+ </p>
171
+ </div>
194
172
 
195
- <template v-else>
196
- <p
197
- v-if="prReview.error"
198
- class="mb-3 rounded-md bg-rose-500/10 px-3 py-2 text-[12px] text-rose-300"
199
- >
200
- {{ prReview.error }}
201
- </p>
173
+ <template v-else>
174
+ <p
175
+ v-if="prReview.error"
176
+ class="mb-3 rounded-md bg-rose-500/10 px-3 py-2 text-[12px] text-rose-300"
177
+ >
178
+ {{ prReview.error }}
179
+ </p>
202
180
 
203
- <!-- The reviewer's overall assessment. -->
204
- <p
205
- v-if="state?.summary"
206
- class="mb-3 rounded-md bg-slate-800/50 px-3 py-2 text-[12px] text-slate-300"
207
- >
208
- <span class="text-slate-500">{{ t('prReview.summaryLabel') }}</span>
209
- {{ state.summary }}
210
- </p>
181
+ <!-- The reviewer's overall assessment. -->
182
+ <p
183
+ v-if="state?.summary"
184
+ class="mb-3 rounded-md bg-slate-800/50 px-3 py-2 text-[12px] text-slate-300"
185
+ >
186
+ <span class="text-slate-500">{{ t('prReview.summaryLabel') }}</span>
187
+ {{ state.summary }}
188
+ </p>
211
189
 
212
- <!-- A clean PR / resolved review with no findings. -->
213
- <div
214
- v-if="findings.length === 0"
215
- class="rounded-xl border border-slate-800 bg-slate-900/60 px-4 py-6 text-center text-[13px] text-slate-300"
216
- >
217
- {{ t('prReview.noFindings') }}
218
- </div>
190
+ <!-- A clean PR / resolved review with no findings. -->
191
+ <div
192
+ v-if="findings.length === 0"
193
+ class="rounded-xl border border-slate-800 bg-slate-900/60 px-4 py-6 text-center text-[13px] text-slate-300"
194
+ >
195
+ {{ t('prReview.noFindings') }}
196
+ </div>
219
197
 
220
- <template v-else>
221
- <!-- Selection toolbar -->
222
- <div v-if="awaiting" class="mb-2 flex items-center gap-3 text-[11px] text-slate-400">
223
- <span data-testid="pr-review-selected-count">
224
- {{ t('prReview.selectedCount', { count: selected.size }) }}
225
- </span>
226
- <button class="text-indigo-300 hover:underline" @click="selectAll">
227
- {{ t('prReview.selectAll') }}
228
- </button>
229
- <button class="text-indigo-300 hover:underline" @click="clearAll">
230
- {{ t('prReview.clear') }}
231
- </button>
232
- </div>
198
+ <template v-else>
199
+ <!-- Selection toolbar -->
200
+ <div v-if="awaiting" class="mb-2 flex items-center gap-3 text-[11px] text-slate-400">
201
+ <span data-testid="pr-review-selected-count">
202
+ {{ t('prReview.selectedCount', { count: selected.size }) }}
203
+ </span>
204
+ <button class="text-indigo-300 hover:underline" @click="selectAll">
205
+ {{ t('prReview.selectAll') }}
206
+ </button>
207
+ <button class="text-indigo-300 hover:underline" @click="clearAll">
208
+ {{ t('prReview.clear') }}
209
+ </button>
210
+ </div>
233
211
 
234
- <!-- Findings grouped by slice -->
235
- <section v-for="g in groups" :key="g.id" class="mb-4">
236
- <h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
237
- {{ g.title }}
238
- </h3>
239
- <p v-if="g.rationale" class="mb-1.5 text-[11px] text-slate-500">
240
- {{ g.rationale }}
241
- </p>
242
- <article
243
- v-for="f in g.items"
244
- :key="f.id"
245
- data-testid="pr-review-finding"
246
- class="mb-1.5 rounded-xl border px-3 py-2 transition"
247
- :class="
248
- awaiting && selected.has(f.id)
249
- ? 'border-indigo-500/60 bg-indigo-500/5'
250
- : 'border-slate-800 bg-slate-900/60'
251
- "
252
- >
253
- <div class="flex items-start gap-2">
254
- <input
255
- v-if="awaiting"
256
- type="checkbox"
257
- class="mt-1 accent-indigo-500"
258
- data-testid="pr-review-finding-toggle"
259
- :checked="selected.has(f.id)"
260
- @change="toggle(f.id)"
261
- />
262
- <div class="min-w-0 flex-1">
263
- <div class="flex flex-wrap items-center gap-1.5">
264
- <span
265
- class="rounded px-1.5 py-0.5 text-[10px] font-semibold uppercase ring-1"
266
- :class="SEVERITY_CLASS[f.severity]"
267
- >
268
- {{ t(`prReview.severity.${f.severity}`) }}
269
- </span>
270
- <span class="rounded bg-slate-800 px-1.5 py-0.5 text-[10px] text-slate-300">
271
- {{ t(`prReview.category.${f.category}`) }}
272
- </span>
273
- <h4 class="min-w-0 flex-1 text-[13px] font-medium text-slate-100">
274
- {{ f.title }}
275
- </h4>
276
- </div>
277
- <p class="mt-0.5 text-[11px] text-slate-500">
278
- {{ f.path
279
- }}<template v-if="f.line != null">
280
- · {{ t('prReview.line', { line: f.line }) }}</template
281
- >
282
- </p>
283
- <p class="mt-1 whitespace-pre-wrap text-[12px] text-slate-300">
284
- {{ f.detail }}
285
- </p>
286
- <p
287
- v-if="f.suggestedFix"
288
- class="mt-1 whitespace-pre-wrap rounded-md bg-slate-800/50 px-2 py-1 text-[11px] text-slate-300"
289
- >
290
- <span class="text-slate-500">{{ t('prReview.suggestedFix') }}</span>
291
- {{ f.suggestedFix }}
292
- </p>
293
- </div>
212
+ <!-- Findings grouped by slice -->
213
+ <section v-for="g in groups" :key="g.id" class="mb-4">
214
+ <h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
215
+ {{ g.title }}
216
+ </h3>
217
+ <p v-if="g.rationale" class="mb-1.5 text-[11px] text-slate-500">
218
+ {{ g.rationale }}
219
+ </p>
220
+ <article
221
+ v-for="f in g.items"
222
+ :key="f.id"
223
+ data-testid="pr-review-finding"
224
+ class="mb-1.5 rounded-xl border px-3 py-2 transition"
225
+ :class="
226
+ awaiting && selected.has(f.id)
227
+ ? 'border-indigo-500/60 bg-indigo-500/5'
228
+ : 'border-slate-800 bg-slate-900/60'
229
+ "
230
+ >
231
+ <div class="flex items-start gap-2">
232
+ <input
233
+ v-if="awaiting"
234
+ type="checkbox"
235
+ class="mt-1 accent-indigo-500"
236
+ data-testid="pr-review-finding-toggle"
237
+ :checked="selected.has(f.id)"
238
+ @change="toggle(f.id)"
239
+ />
240
+ <div class="min-w-0 flex-1">
241
+ <div class="flex flex-wrap items-center gap-1.5">
242
+ <span
243
+ class="rounded px-1.5 py-0.5 text-[10px] font-semibold uppercase ring-1"
244
+ :class="SEVERITY_CLASS[f.severity]"
245
+ >
246
+ {{ t(`prReview.severity.${f.severity}`) }}
247
+ </span>
248
+ <span class="rounded bg-slate-800 px-1.5 py-0.5 text-[10px] text-slate-300">
249
+ {{ t(`prReview.category.${f.category}`) }}
250
+ </span>
251
+ <h4 class="min-w-0 flex-1 text-[13px] font-medium text-slate-100">
252
+ {{ f.title }}
253
+ </h4>
294
254
  </div>
295
- </article>
296
- </section>
297
- </template>
298
- </template>
299
- </div>
300
-
301
- <!-- Footer -->
302
- <footer
303
- v-if="awaiting"
304
- class="flex items-center justify-end gap-2 border-t border-slate-800 px-5 py-3"
305
- >
306
- <UButton
307
- color="neutral"
308
- variant="ghost"
309
- :disabled="!canResolve || !access.canExecuteRuns.value"
310
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
311
- data-testid="pr-review-finish"
312
- @click="onResolve('finish')"
313
- >
314
- {{ t('prReview.finish') }}
315
- </UButton>
316
- <UButton
317
- color="neutral"
318
- variant="soft"
319
- :disabled="!canResolve || !hasSelection || !access.canExecuteRuns.value"
320
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
321
- data-testid="pr-review-post"
322
- @click="onResolve('post')"
323
- >
324
- {{ t('prReview.post') }}
325
- </UButton>
326
- <UButton
327
- color="primary"
328
- :loading="prReview.resolving"
329
- :disabled="!canResolve || !hasSelection || !access.canExecuteRuns.value"
330
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
331
- data-testid="pr-review-fix"
332
- @click="onResolve('fix')"
333
- >
334
- {{ t('prReview.fix') }}
335
- </UButton>
336
- </footer>
337
- </div>
255
+ <p class="mt-0.5 text-[11px] text-slate-500">
256
+ {{ f.path
257
+ }}<template v-if="f.line != null">
258
+ · {{ t('prReview.line', { line: f.line }) }}</template
259
+ >
260
+ </p>
261
+ <p class="mt-1 whitespace-pre-wrap text-[12px] text-slate-300">
262
+ {{ f.detail }}
263
+ </p>
264
+ <p
265
+ v-if="f.suggestedFix"
266
+ class="mt-1 whitespace-pre-wrap rounded-md bg-slate-800/50 px-2 py-1 text-[11px] text-slate-300"
267
+ >
268
+ <span class="text-slate-500">{{ t('prReview.suggestedFix') }}</span>
269
+ {{ f.suggestedFix }}
270
+ </p>
271
+ </div>
272
+ </div>
273
+ </article>
274
+ </section>
275
+ </template>
276
+ </template>
338
277
  </div>
339
- </Teleport>
278
+
279
+ <!-- Footer -->
280
+ <footer
281
+ v-if="awaiting"
282
+ class="flex items-center justify-end gap-2 border-t border-slate-800 px-5 py-3"
283
+ >
284
+ <UButton
285
+ color="neutral"
286
+ variant="ghost"
287
+ :disabled="!canResolve || !access.canExecuteRuns.value"
288
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
289
+ data-testid="pr-review-finish"
290
+ @click="onResolve('finish')"
291
+ >
292
+ {{ t('prReview.finish') }}
293
+ </UButton>
294
+ <UButton
295
+ color="neutral"
296
+ variant="soft"
297
+ :disabled="!canResolve || !hasSelection || !access.canExecuteRuns.value"
298
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
299
+ data-testid="pr-review-post"
300
+ @click="onResolve('post')"
301
+ >
302
+ {{ t('prReview.post') }}
303
+ </UButton>
304
+ <UButton
305
+ color="primary"
306
+ :loading="prReview.resolving"
307
+ :disabled="!canResolve || !hasSelection || !access.canExecuteRuns.value"
308
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
309
+ data-testid="pr-review-fix"
310
+ @click="onResolve('fix')"
311
+ >
312
+ {{ t('prReview.fix') }}
313
+ </UButton>
314
+ </footer>
315
+ </ResultWindowShell>
340
316
  </template>
@@ -15,11 +15,9 @@ const board = useBoardStore()
15
15
  const execution = useExecutionStore()
16
16
  const { t, d } = useI18n()
17
17
 
18
- // `manageEscape: false` — `ResultWindowShell` owns Escape (and focus trap + scroll lock +
19
- // stacking) via the shared overlay behaviour.
20
- const { open, blockId, instanceId, stepIndex, close } = useResultView('ralph-loop', {
21
- manageEscape: false,
22
- })
18
+ // `ResultWindowShell` owns Escape (and focus trap + scroll lock + stacking) via the shared
19
+ // overlay behaviour.
20
+ const { open, blockId, instanceId, stepIndex, close } = useResultView('ralph-loop')
23
21
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
24
22
  const headerTitle = computed(
25
23
  () => `${meta.value.label}${block.value ? ` — ${block.value.title}` : ''}`,