@cat-factory/app 0.138.1 → 0.139.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/app/components/brainstorm/BrainstormWindow.vue +338 -374
- package/app/components/clarity/ClarityReviewWindow.vue +323 -353
- package/app/components/consensus/ConsensusSessionWindow.vue +138 -150
- package/app/components/docs/DocInterviewWindow.vue +108 -136
- package/app/components/followUp/FollowUpWindow.vue +3 -5
- package/app/components/forkDecision/ForkDecisionWindow.vue +1 -3
- package/app/components/gates/GateResultView.vue +3 -5
- package/app/components/humanTest/HumanTestWindow.vue +3 -5
- package/app/components/initiative/InitiativePlanningWindow.vue +89 -112
- package/app/components/initiative/InitiativeTrackerWindow.vue +389 -424
- package/app/components/panels/GenericStructuredResultView.vue +3 -5
- package/app/components/panels/MergerResultView.vue +3 -5
- package/app/components/panels/ResultWindowShell.vue +1 -1
- package/app/components/prReview/PrReviewWindow.vue +181 -205
- package/app/components/ralph/RalphLoopResultView.vue +3 -5
- package/app/components/requirements/RequirementsReviewWindow.vue +512 -557
- package/app/components/spec/ServiceSpecWindow.vue +235 -266
- package/app/components/testing/TestReportWindow.vue +4 -6
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +3 -5
- package/app/composables/useResultView.ts +10 -21
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
initiativeProgress,
|
|
19
19
|
pendingCheckpointPhase,
|
|
20
20
|
} from '~/utils/initiative'
|
|
21
|
+
import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
21
22
|
|
|
22
23
|
const board = useBoardStore()
|
|
23
24
|
const initiatives = useInitiativesStore()
|
|
@@ -173,459 +174,423 @@ async function savePolicy() {
|
|
|
173
174
|
</script>
|
|
174
175
|
|
|
175
176
|
<template>
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
<ResultWindowShell
|
|
178
|
+
:open="open"
|
|
179
|
+
icon="i-lucide-milestone"
|
|
180
|
+
icon-class="bg-indigo-500/15 text-indigo-300"
|
|
181
|
+
:title="initiative?.title ?? block?.title ?? t('initiative.tracker.title')"
|
|
182
|
+
:subtitle="t('initiative.tracker.subtitle')"
|
|
183
|
+
width="4xl"
|
|
184
|
+
testid="initiative-tracker-window"
|
|
185
|
+
@close="close"
|
|
186
|
+
>
|
|
187
|
+
<template #header-extras>
|
|
188
|
+
<div v-if="progress" class="flex items-center gap-2" data-testid="initiative-progress">
|
|
189
|
+
<div class="h-1.5 w-24 overflow-hidden rounded-full bg-slate-800">
|
|
190
|
+
<div
|
|
191
|
+
class="h-full rounded-full bg-emerald-500 transition-[width] duration-500"
|
|
192
|
+
:style="{ width: `${progressPct}%` }"
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
<span class="text-[11px] tabular-nums text-slate-400">
|
|
196
|
+
{{ t('initiative.card.progress', { done: progress.settled, total: progress.total }) }}
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
<UBadge v-if="initiative" color="primary" variant="subtle" size="sm">
|
|
200
|
+
{{ t(INITIATIVE_STATUS_LABEL_KEYS[initiative.status]) }}
|
|
201
|
+
</UBadge>
|
|
202
|
+
</template>
|
|
203
|
+
|
|
204
|
+
<div class="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
|
205
|
+
<!-- No entity yet (module unwired / still creating) -->
|
|
182
206
|
<div
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
aria-modal="true"
|
|
186
|
-
data-testid="initiative-tracker-window"
|
|
207
|
+
v-if="!initiative"
|
|
208
|
+
class="flex h-full flex-col items-center justify-center gap-2 text-center text-slate-400"
|
|
187
209
|
>
|
|
188
|
-
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
210
|
+
<UIcon name="i-lucide-milestone" class="h-8 w-8 opacity-40" />
|
|
211
|
+
<p class="text-sm">{{ t('initiative.tracker.empty') }}</p>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<template v-else>
|
|
215
|
+
<!-- Paused at a phase checkpoint (D2): a completed checkpoint phase is awaiting
|
|
216
|
+
review before the next phase spawns. Read the phase's artifacts/PRs below,
|
|
217
|
+
then resume (continue) or cancel (stop) the initiative right here. -->
|
|
218
|
+
<section
|
|
219
|
+
v-if="pausedAtCheckpoint"
|
|
220
|
+
class="mb-4 rounded-lg border border-amber-500/40 bg-amber-500/10 p-3.5"
|
|
221
|
+
data-testid="initiative-checkpoint-pause"
|
|
222
|
+
>
|
|
223
|
+
<div class="flex items-start gap-2.5">
|
|
224
|
+
<UIcon name="i-lucide-pause-circle" class="mt-0.5 h-4 w-4 shrink-0 text-amber-300" />
|
|
225
|
+
<div class="min-w-0 flex-1">
|
|
226
|
+
<h3 class="text-[13px] font-semibold text-amber-200">
|
|
227
|
+
{{ t('initiative.checkpoint.pausedTitle') }}
|
|
228
|
+
</h3>
|
|
229
|
+
<p class="mt-0.5 text-[12px] leading-relaxed text-amber-100/80">
|
|
230
|
+
{{ t('initiative.checkpoint.pausedBody', { phase: checkpointPhase!.title }) }}
|
|
231
|
+
</p>
|
|
232
|
+
<div class="mt-2.5 flex flex-wrap gap-2">
|
|
233
|
+
<button
|
|
234
|
+
class="rounded bg-indigo-600 px-2.5 py-1 text-[11px] font-medium text-white hover:bg-indigo-500 disabled:opacity-50"
|
|
235
|
+
:disabled="initiatives.controlling"
|
|
236
|
+
data-testid="initiative-checkpoint-resume"
|
|
237
|
+
@click="checkpointControl('resume')"
|
|
238
|
+
>
|
|
239
|
+
{{ t('initiative.inspector.resume') }}
|
|
240
|
+
</button>
|
|
241
|
+
<button
|
|
242
|
+
class="rounded border border-rose-500/50 px-2.5 py-1 text-[11px] font-medium text-rose-300 hover:bg-rose-500/10 disabled:opacity-50"
|
|
243
|
+
:disabled="initiatives.controlling"
|
|
244
|
+
data-testid="initiative-checkpoint-cancel"
|
|
245
|
+
@click="checkpointControl('cancel')"
|
|
246
|
+
>
|
|
247
|
+
{{ t('initiative.inspector.cancel') }}
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
209
250
|
</div>
|
|
210
|
-
<span class="text-[11px] tabular-nums text-slate-400">
|
|
211
|
-
{{ t('initiative.card.progress', { done: progress.settled, total: progress.total }) }}
|
|
212
|
-
</span>
|
|
213
251
|
</div>
|
|
214
|
-
|
|
215
|
-
{{ t(INITIATIVE_STATUS_LABEL_KEYS[initiative.status]) }}
|
|
216
|
-
</UBadge>
|
|
217
|
-
<button
|
|
218
|
-
class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
|
|
219
|
-
@click="close"
|
|
220
|
-
>
|
|
221
|
-
<UIcon name="i-lucide-x" class="h-4 w-4" />
|
|
222
|
-
</button>
|
|
223
|
-
</header>
|
|
252
|
+
</section>
|
|
224
253
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
254
|
+
<!-- Goal & constraints -->
|
|
255
|
+
<section v-if="initiative.goal" class="mb-4">
|
|
256
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
257
|
+
{{ t('initiative.tracker.goal') }}
|
|
258
|
+
</h3>
|
|
259
|
+
<p class="whitespace-pre-wrap text-[13px] leading-relaxed text-slate-300">
|
|
260
|
+
{{ initiative.goal }}
|
|
261
|
+
</p>
|
|
262
|
+
</section>
|
|
263
|
+
<section v-if="initiative.constraints?.length" class="mb-4">
|
|
264
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
265
|
+
{{ t('initiative.tracker.constraints') }}
|
|
266
|
+
</h3>
|
|
267
|
+
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
268
|
+
<li v-for="(c, i) in initiative.constraints" :key="i">{{ c }}</li>
|
|
269
|
+
</ul>
|
|
270
|
+
</section>
|
|
271
|
+
<section v-if="initiative.nonGoals?.length" class="mb-4">
|
|
272
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
273
|
+
{{ t('initiative.tracker.nonGoals') }}
|
|
274
|
+
</h3>
|
|
275
|
+
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
276
|
+
<li v-for="(g, i) in initiative.nonGoals" :key="i">{{ g }}</li>
|
|
277
|
+
</ul>
|
|
278
|
+
</section>
|
|
279
|
+
<section v-if="initiative.analysisSummary" class="mb-4">
|
|
280
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
281
|
+
{{ t('initiative.tracker.analysis') }}
|
|
282
|
+
</h3>
|
|
283
|
+
<p class="whitespace-pre-wrap text-[13px] leading-relaxed text-slate-300">
|
|
284
|
+
{{ initiative.analysisSummary }}
|
|
285
|
+
</p>
|
|
286
|
+
</section>
|
|
234
287
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
288
|
+
<!-- Awaiting planning -->
|
|
289
|
+
<div
|
|
290
|
+
v-if="phases.length === 0"
|
|
291
|
+
class="mb-4 rounded-lg border border-dashed border-slate-700 p-4 text-center text-[12px] text-slate-400"
|
|
292
|
+
>
|
|
293
|
+
{{ t('initiative.tracker.noPlan') }}
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<!-- Phases + items -->
|
|
297
|
+
<section v-for="phase in phases" :key="phase.id" class="mb-5">
|
|
298
|
+
<h3 class="mb-1 flex items-center gap-2 text-sm font-semibold text-slate-200">
|
|
299
|
+
<span>{{ t('initiative.tracker.phase', { title: phase.title }) }}</span>
|
|
300
|
+
<!-- Checkpoint annotation (D2): this phase pauses the initiative for human review
|
|
301
|
+
once its items settle. Cleared → already reviewed; the pending one → awaiting
|
|
302
|
+
review; otherwise an upcoming gate. -->
|
|
303
|
+
<UBadge
|
|
304
|
+
v-if="phase.checkpoint"
|
|
305
|
+
:color="
|
|
306
|
+
phase.checkpointClearedAt
|
|
307
|
+
? 'neutral'
|
|
308
|
+
: awaitingReviewPhaseId === phase.id
|
|
309
|
+
? 'warning'
|
|
310
|
+
: 'info'
|
|
311
|
+
"
|
|
312
|
+
variant="subtle"
|
|
313
|
+
size="sm"
|
|
314
|
+
:data-testid="`initiative-phase-checkpoint-${phase.id}`"
|
|
243
315
|
>
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
316
|
+
<UIcon name="i-lucide-flag" class="mr-1 h-3 w-3" />
|
|
317
|
+
{{
|
|
318
|
+
phase.checkpointClearedAt
|
|
319
|
+
? t('initiative.checkpoint.cleared')
|
|
320
|
+
: awaitingReviewPhaseId === phase.id
|
|
321
|
+
? t('initiative.checkpoint.awaiting')
|
|
322
|
+
: t('initiative.checkpoint.badge')
|
|
323
|
+
}}
|
|
324
|
+
</UBadge>
|
|
325
|
+
</h3>
|
|
326
|
+
<p v-if="phase.goal" class="mb-2 text-[12px] text-slate-400">{{ phase.goal }}</p>
|
|
327
|
+
<div class="overflow-x-auto rounded-lg border border-slate-800">
|
|
328
|
+
<table class="w-full text-[12px]">
|
|
329
|
+
<thead>
|
|
330
|
+
<tr class="border-b border-slate-800 text-left text-slate-500">
|
|
331
|
+
<th class="px-3 py-2 font-medium">{{ t('initiative.tracker.colItem') }}</th>
|
|
332
|
+
<th class="px-3 py-2 font-medium">{{ t('initiative.tracker.colStatus') }}</th>
|
|
333
|
+
<th class="px-3 py-2 font-medium">{{ t('initiative.tracker.colPr') }}</th>
|
|
334
|
+
</tr>
|
|
335
|
+
</thead>
|
|
336
|
+
<tbody>
|
|
337
|
+
<tr
|
|
338
|
+
v-for="item in itemsOf(phase.id)"
|
|
339
|
+
:key="item.id"
|
|
340
|
+
class="border-b border-slate-800/60 last:border-0"
|
|
341
|
+
>
|
|
342
|
+
<td class="px-3 py-2 align-top">
|
|
343
|
+
<div class="font-medium text-slate-200">{{ item.title }}</div>
|
|
344
|
+
<div v-if="item.dependsOn?.length" class="mt-0.5 text-[10px] text-slate-500">
|
|
345
|
+
{{
|
|
346
|
+
t('initiative.tracker.dependsOn', {
|
|
347
|
+
items: item.dependsOn.join(', '),
|
|
348
|
+
})
|
|
349
|
+
}}
|
|
350
|
+
</div>
|
|
351
|
+
<div v-if="item.note" class="mt-0.5 text-[10px] text-amber-300/80">
|
|
352
|
+
{{ item.note }}
|
|
353
|
+
</div>
|
|
354
|
+
<div
|
|
355
|
+
v-if="editable && (item.status === 'blocked' || item.status === 'pending')"
|
|
356
|
+
class="mt-1 flex gap-1.5"
|
|
262
357
|
>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
358
|
+
<button
|
|
359
|
+
v-if="item.status === 'blocked'"
|
|
360
|
+
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800 disabled:opacity-50"
|
|
361
|
+
:disabled="initiatives.curating"
|
|
362
|
+
:data-testid="`initiative-item-retry-${item.id}`"
|
|
363
|
+
@click="itemAction(item, 'retry')"
|
|
364
|
+
>
|
|
365
|
+
{{ t('initiative.curation.retry') }}
|
|
366
|
+
</button>
|
|
367
|
+
<button
|
|
368
|
+
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800 disabled:opacity-50"
|
|
369
|
+
:disabled="initiatives.curating"
|
|
370
|
+
:data-testid="`initiative-item-skip-${item.id}`"
|
|
371
|
+
@click="itemAction(item, 'skip')"
|
|
372
|
+
>
|
|
373
|
+
{{ t('initiative.curation.skip') }}
|
|
374
|
+
</button>
|
|
375
|
+
</div>
|
|
376
|
+
</td>
|
|
377
|
+
<td class="px-3 py-2 align-top">
|
|
378
|
+
<UBadge
|
|
379
|
+
:color="INITIATIVE_ITEM_STATUS_CHIPS[item.status]"
|
|
380
|
+
variant="subtle"
|
|
381
|
+
size="sm"
|
|
270
382
|
>
|
|
271
|
-
{{ t(
|
|
272
|
-
</
|
|
273
|
-
</
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
292
|
-
<li v-for="(c, i) in initiative.constraints" :key="i">{{ c }}</li>
|
|
293
|
-
</ul>
|
|
294
|
-
</section>
|
|
295
|
-
<section v-if="initiative.nonGoals?.length" class="mb-4">
|
|
296
|
-
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
297
|
-
{{ t('initiative.tracker.nonGoals') }}
|
|
298
|
-
</h3>
|
|
299
|
-
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
300
|
-
<li v-for="(g, i) in initiative.nonGoals" :key="i">{{ g }}</li>
|
|
301
|
-
</ul>
|
|
302
|
-
</section>
|
|
303
|
-
<section v-if="initiative.analysisSummary" class="mb-4">
|
|
304
|
-
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
305
|
-
{{ t('initiative.tracker.analysis') }}
|
|
306
|
-
</h3>
|
|
307
|
-
<p class="whitespace-pre-wrap text-[13px] leading-relaxed text-slate-300">
|
|
308
|
-
{{ initiative.analysisSummary }}
|
|
309
|
-
</p>
|
|
310
|
-
</section>
|
|
383
|
+
{{ t(INITIATIVE_ITEM_STATUS_LABEL_KEYS[item.status]) }}
|
|
384
|
+
</UBadge>
|
|
385
|
+
</td>
|
|
386
|
+
<td class="px-3 py-2 align-top">
|
|
387
|
+
<a
|
|
388
|
+
v-if="item.pr"
|
|
389
|
+
:href="item.pr.url"
|
|
390
|
+
target="_blank"
|
|
391
|
+
rel="noopener"
|
|
392
|
+
class="text-sky-400 hover:underline"
|
|
393
|
+
>
|
|
394
|
+
{{ item.pr.number ? `#${item.pr.number}` : t('initiative.tracker.prLink') }}
|
|
395
|
+
</a>
|
|
396
|
+
<span v-else class="text-slate-600">—</span>
|
|
397
|
+
</td>
|
|
398
|
+
</tr>
|
|
399
|
+
</tbody>
|
|
400
|
+
</table>
|
|
401
|
+
</div>
|
|
402
|
+
</section>
|
|
311
403
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
404
|
+
<!-- Execution policy -->
|
|
405
|
+
<section v-if="initiative.policy" class="mb-4">
|
|
406
|
+
<div class="mb-1 flex items-center gap-2">
|
|
407
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
408
|
+
{{ t('initiative.tracker.policy') }}
|
|
409
|
+
</h3>
|
|
410
|
+
<button
|
|
411
|
+
v-if="editable && !editingPolicy"
|
|
412
|
+
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800"
|
|
413
|
+
data-testid="initiative-policy-edit"
|
|
414
|
+
@click="startEditPolicy"
|
|
316
415
|
>
|
|
317
|
-
{{ t('initiative.
|
|
416
|
+
{{ t('initiative.curation.edit') }}
|
|
417
|
+
</button>
|
|
418
|
+
</div>
|
|
419
|
+
<ul v-if="!editingPolicy" class="text-[12px] text-slate-300">
|
|
420
|
+
<li>
|
|
421
|
+
{{
|
|
422
|
+
t('initiative.tracker.maxConcurrent', {
|
|
423
|
+
count: initiative.policy.maxConcurrent,
|
|
424
|
+
})
|
|
425
|
+
}}
|
|
426
|
+
</li>
|
|
427
|
+
<li v-for="(rule, i) in policyRules" :key="i">
|
|
428
|
+
<code class="text-sky-300">{{ rule.pipelineId }}</code>
|
|
429
|
+
· {{ ruleAxes(rule) }}
|
|
430
|
+
</li>
|
|
431
|
+
<li>
|
|
432
|
+
{{ t('initiative.tracker.defaultPipeline') }}
|
|
433
|
+
<code class="text-sky-300">{{ initiative.policy.defaultPipelineId }}</code>
|
|
434
|
+
</li>
|
|
435
|
+
</ul>
|
|
436
|
+
<!-- Edit form: the two scalar knobs; planner-authored rules are preserved. -->
|
|
437
|
+
<div v-else class="flex flex-col gap-2 rounded-lg border border-slate-800 p-3">
|
|
438
|
+
<label class="flex items-center gap-2 text-[12px] text-slate-300">
|
|
439
|
+
<span class="w-40">{{ t('initiative.curation.maxConcurrentField') }}</span>
|
|
440
|
+
<input
|
|
441
|
+
v-model.number="policyForm.maxConcurrent"
|
|
442
|
+
type="number"
|
|
443
|
+
min="1"
|
|
444
|
+
max="20"
|
|
445
|
+
class="w-20 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-slate-200"
|
|
446
|
+
data-testid="initiative-policy-max-concurrent"
|
|
447
|
+
/>
|
|
448
|
+
</label>
|
|
449
|
+
<label class="flex items-center gap-2 text-[12px] text-slate-300">
|
|
450
|
+
<span class="w-40">{{ t('initiative.curation.defaultPipelineField') }}</span>
|
|
451
|
+
<input
|
|
452
|
+
v-model="policyForm.defaultPipelineId"
|
|
453
|
+
type="text"
|
|
454
|
+
class="flex-1 rounded border border-slate-700 bg-slate-950 px-2 py-1 font-mono text-[11px] text-slate-200"
|
|
455
|
+
data-testid="initiative-policy-default-pipeline"
|
|
456
|
+
/>
|
|
457
|
+
</label>
|
|
458
|
+
<div class="flex gap-2">
|
|
459
|
+
<button
|
|
460
|
+
class="rounded bg-indigo-600 px-2 py-1 text-[11px] text-white hover:bg-indigo-500 disabled:opacity-50"
|
|
461
|
+
:disabled="initiatives.curating"
|
|
462
|
+
data-testid="initiative-policy-save"
|
|
463
|
+
@click="savePolicy"
|
|
464
|
+
>
|
|
465
|
+
{{ t('initiative.curation.save') }}
|
|
466
|
+
</button>
|
|
467
|
+
<button
|
|
468
|
+
class="rounded border border-slate-700 px-2 py-1 text-[11px] text-slate-300 hover:bg-slate-800"
|
|
469
|
+
@click="editingPolicy = false"
|
|
470
|
+
>
|
|
471
|
+
{{ t('initiative.curation.cancel') }}
|
|
472
|
+
</button>
|
|
318
473
|
</div>
|
|
474
|
+
</div>
|
|
475
|
+
</section>
|
|
319
476
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
477
|
+
<!-- Logs -->
|
|
478
|
+
<section v-if="initiative.decisions?.length" class="mb-4">
|
|
479
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
480
|
+
{{ t('initiative.tracker.decisions') }}
|
|
481
|
+
</h3>
|
|
482
|
+
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
483
|
+
<li v-for="d in initiative.decisions" :key="d.id">
|
|
484
|
+
<span class="font-medium">{{ d.title }}</span>
|
|
485
|
+
<span v-if="d.detail" class="text-slate-400"> — {{ d.detail }}</span>
|
|
486
|
+
</li>
|
|
487
|
+
</ul>
|
|
488
|
+
</section>
|
|
489
|
+
<section v-if="initiative.deviations?.length" class="mb-4">
|
|
490
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
491
|
+
{{ t('initiative.tracker.deviations') }}
|
|
492
|
+
</h3>
|
|
493
|
+
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
494
|
+
<li v-for="d in initiative.deviations" :key="d.id">
|
|
495
|
+
<code v-if="d.itemId" class="text-slate-400">{{ d.itemId }}</code>
|
|
496
|
+
{{ d.description }}
|
|
497
|
+
<span v-if="d.resolution" class="text-slate-400"> → {{ d.resolution }}</span>
|
|
498
|
+
</li>
|
|
499
|
+
</ul>
|
|
500
|
+
</section>
|
|
501
|
+
<section v-if="initiative.followUps?.length" class="mb-4">
|
|
502
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
503
|
+
{{ t('initiative.tracker.followUps') }}
|
|
504
|
+
</h3>
|
|
505
|
+
<ul class="flex flex-col gap-2 text-[13px] text-slate-300">
|
|
506
|
+
<li
|
|
507
|
+
v-for="f in initiative.followUps"
|
|
508
|
+
:key="f.id"
|
|
509
|
+
class="rounded-lg border border-slate-800 p-2.5"
|
|
510
|
+
:data-testid="`initiative-followup-${f.id}`"
|
|
511
|
+
>
|
|
512
|
+
<div class="flex items-start gap-2">
|
|
513
|
+
<div class="min-w-0 flex-1">
|
|
514
|
+
<span class="font-medium">{{ f.title }}</span>
|
|
515
|
+
<span v-if="f.detail" class="text-slate-400"> — {{ f.detail }}</span>
|
|
516
|
+
</div>
|
|
327
517
|
<UBadge
|
|
328
|
-
|
|
329
|
-
:color="
|
|
330
|
-
phase.checkpointClearedAt
|
|
331
|
-
? 'neutral'
|
|
332
|
-
: awaitingReviewPhaseId === phase.id
|
|
333
|
-
? 'warning'
|
|
334
|
-
: 'info'
|
|
335
|
-
"
|
|
518
|
+
:color="INITIATIVE_FOLLOWUP_STATUS_CHIPS[f.status]"
|
|
336
519
|
variant="subtle"
|
|
337
520
|
size="sm"
|
|
338
|
-
:data-testid="`initiative-phase-checkpoint-${phase.id}`"
|
|
339
521
|
>
|
|
340
|
-
|
|
341
|
-
{{
|
|
342
|
-
phase.checkpointClearedAt
|
|
343
|
-
? t('initiative.checkpoint.cleared')
|
|
344
|
-
: awaitingReviewPhaseId === phase.id
|
|
345
|
-
? t('initiative.checkpoint.awaiting')
|
|
346
|
-
: t('initiative.checkpoint.badge')
|
|
347
|
-
}}
|
|
522
|
+
{{ t(INITIATIVE_FOLLOWUP_STATUS_LABEL_KEYS[f.status]) }}
|
|
348
523
|
</UBadge>
|
|
349
|
-
</h3>
|
|
350
|
-
<p v-if="phase.goal" class="mb-2 text-[12px] text-slate-400">{{ phase.goal }}</p>
|
|
351
|
-
<div class="overflow-x-auto rounded-lg border border-slate-800">
|
|
352
|
-
<table class="w-full text-[12px]">
|
|
353
|
-
<thead>
|
|
354
|
-
<tr class="border-b border-slate-800 text-left text-slate-500">
|
|
355
|
-
<th class="px-3 py-2 font-medium">{{ t('initiative.tracker.colItem') }}</th>
|
|
356
|
-
<th class="px-3 py-2 font-medium">{{ t('initiative.tracker.colStatus') }}</th>
|
|
357
|
-
<th class="px-3 py-2 font-medium">{{ t('initiative.tracker.colPr') }}</th>
|
|
358
|
-
</tr>
|
|
359
|
-
</thead>
|
|
360
|
-
<tbody>
|
|
361
|
-
<tr
|
|
362
|
-
v-for="item in itemsOf(phase.id)"
|
|
363
|
-
:key="item.id"
|
|
364
|
-
class="border-b border-slate-800/60 last:border-0"
|
|
365
|
-
>
|
|
366
|
-
<td class="px-3 py-2 align-top">
|
|
367
|
-
<div class="font-medium text-slate-200">{{ item.title }}</div>
|
|
368
|
-
<div
|
|
369
|
-
v-if="item.dependsOn?.length"
|
|
370
|
-
class="mt-0.5 text-[10px] text-slate-500"
|
|
371
|
-
>
|
|
372
|
-
{{
|
|
373
|
-
t('initiative.tracker.dependsOn', {
|
|
374
|
-
items: item.dependsOn.join(', '),
|
|
375
|
-
})
|
|
376
|
-
}}
|
|
377
|
-
</div>
|
|
378
|
-
<div v-if="item.note" class="mt-0.5 text-[10px] text-amber-300/80">
|
|
379
|
-
{{ item.note }}
|
|
380
|
-
</div>
|
|
381
|
-
<div
|
|
382
|
-
v-if="
|
|
383
|
-
editable && (item.status === 'blocked' || item.status === 'pending')
|
|
384
|
-
"
|
|
385
|
-
class="mt-1 flex gap-1.5"
|
|
386
|
-
>
|
|
387
|
-
<button
|
|
388
|
-
v-if="item.status === 'blocked'"
|
|
389
|
-
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800 disabled:opacity-50"
|
|
390
|
-
:disabled="initiatives.curating"
|
|
391
|
-
:data-testid="`initiative-item-retry-${item.id}`"
|
|
392
|
-
@click="itemAction(item, 'retry')"
|
|
393
|
-
>
|
|
394
|
-
{{ t('initiative.curation.retry') }}
|
|
395
|
-
</button>
|
|
396
|
-
<button
|
|
397
|
-
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800 disabled:opacity-50"
|
|
398
|
-
:disabled="initiatives.curating"
|
|
399
|
-
:data-testid="`initiative-item-skip-${item.id}`"
|
|
400
|
-
@click="itemAction(item, 'skip')"
|
|
401
|
-
>
|
|
402
|
-
{{ t('initiative.curation.skip') }}
|
|
403
|
-
</button>
|
|
404
|
-
</div>
|
|
405
|
-
</td>
|
|
406
|
-
<td class="px-3 py-2 align-top">
|
|
407
|
-
<UBadge
|
|
408
|
-
:color="INITIATIVE_ITEM_STATUS_CHIPS[item.status]"
|
|
409
|
-
variant="subtle"
|
|
410
|
-
size="sm"
|
|
411
|
-
>
|
|
412
|
-
{{ t(INITIATIVE_ITEM_STATUS_LABEL_KEYS[item.status]) }}
|
|
413
|
-
</UBadge>
|
|
414
|
-
</td>
|
|
415
|
-
<td class="px-3 py-2 align-top">
|
|
416
|
-
<a
|
|
417
|
-
v-if="item.pr"
|
|
418
|
-
:href="item.pr.url"
|
|
419
|
-
target="_blank"
|
|
420
|
-
rel="noopener"
|
|
421
|
-
class="text-sky-400 hover:underline"
|
|
422
|
-
>
|
|
423
|
-
{{
|
|
424
|
-
item.pr.number ? `#${item.pr.number}` : t('initiative.tracker.prLink')
|
|
425
|
-
}}
|
|
426
|
-
</a>
|
|
427
|
-
<span v-else class="text-slate-600">—</span>
|
|
428
|
-
</td>
|
|
429
|
-
</tr>
|
|
430
|
-
</tbody>
|
|
431
|
-
</table>
|
|
432
524
|
</div>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
</button>
|
|
449
|
-
</div>
|
|
450
|
-
<ul v-if="!editingPolicy" class="text-[12px] text-slate-300">
|
|
451
|
-
<li>
|
|
452
|
-
{{
|
|
453
|
-
t('initiative.tracker.maxConcurrent', {
|
|
454
|
-
count: initiative.policy.maxConcurrent,
|
|
455
|
-
})
|
|
456
|
-
}}
|
|
457
|
-
</li>
|
|
458
|
-
<li v-for="(rule, i) in policyRules" :key="i">
|
|
459
|
-
<code class="text-sky-300">{{ rule.pipelineId }}</code>
|
|
460
|
-
· {{ ruleAxes(rule) }}
|
|
461
|
-
</li>
|
|
462
|
-
<li>
|
|
463
|
-
{{ t('initiative.tracker.defaultPipeline') }}
|
|
464
|
-
<code class="text-sky-300">{{ initiative.policy.defaultPipelineId }}</code>
|
|
465
|
-
</li>
|
|
466
|
-
</ul>
|
|
467
|
-
<!-- Edit form: the two scalar knobs; planner-authored rules are preserved. -->
|
|
468
|
-
<div v-else class="flex flex-col gap-2 rounded-lg border border-slate-800 p-3">
|
|
469
|
-
<label class="flex items-center gap-2 text-[12px] text-slate-300">
|
|
470
|
-
<span class="w-40">{{ t('initiative.curation.maxConcurrentField') }}</span>
|
|
471
|
-
<input
|
|
472
|
-
v-model.number="policyForm.maxConcurrent"
|
|
473
|
-
type="number"
|
|
474
|
-
min="1"
|
|
475
|
-
max="20"
|
|
476
|
-
class="w-20 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-slate-200"
|
|
477
|
-
data-testid="initiative-policy-max-concurrent"
|
|
478
|
-
/>
|
|
479
|
-
</label>
|
|
480
|
-
<label class="flex items-center gap-2 text-[12px] text-slate-300">
|
|
481
|
-
<span class="w-40">{{ t('initiative.curation.defaultPipelineField') }}</span>
|
|
525
|
+
<!-- Triage actions for an open follow-up (only while executing) -->
|
|
526
|
+
<div v-if="editable && f.status === 'open'" class="mt-2">
|
|
527
|
+
<div v-if="promotingId === f.id" class="flex flex-col gap-2">
|
|
528
|
+
<label class="flex items-center gap-2 text-[12px]">
|
|
529
|
+
<span class="text-slate-400">{{ t('initiative.curation.phaseField') }}</span>
|
|
530
|
+
<select
|
|
531
|
+
v-model="promoteForm.phaseId"
|
|
532
|
+
class="flex-1 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-slate-200"
|
|
533
|
+
data-testid="initiative-promote-phase"
|
|
534
|
+
>
|
|
535
|
+
<option v-for="p in phases" :key="p.id" :value="p.id">
|
|
536
|
+
{{ p.title }}
|
|
537
|
+
</option>
|
|
538
|
+
</select>
|
|
539
|
+
</label>
|
|
482
540
|
<input
|
|
483
|
-
v-model="
|
|
541
|
+
v-model="promoteForm.title"
|
|
484
542
|
type="text"
|
|
485
|
-
class="
|
|
486
|
-
|
|
543
|
+
class="rounded border border-slate-700 bg-slate-950 px-2 py-1 text-[12px] text-slate-200"
|
|
544
|
+
:placeholder="t('initiative.curation.itemTitlePlaceholder')"
|
|
545
|
+
data-testid="initiative-promote-title"
|
|
487
546
|
/>
|
|
488
|
-
|
|
489
|
-
|
|
547
|
+
<div class="flex gap-2">
|
|
548
|
+
<button
|
|
549
|
+
class="rounded bg-indigo-600 px-2 py-1 text-[11px] text-white hover:bg-indigo-500 disabled:opacity-50"
|
|
550
|
+
:disabled="initiatives.curating || !promoteForm.phaseId"
|
|
551
|
+
data-testid="initiative-promote-submit"
|
|
552
|
+
@click="submitPromote(f)"
|
|
553
|
+
>
|
|
554
|
+
{{ t('initiative.curation.promoteConfirm') }}
|
|
555
|
+
</button>
|
|
556
|
+
<button
|
|
557
|
+
class="rounded border border-slate-700 px-2 py-1 text-[11px] text-slate-300 hover:bg-slate-800"
|
|
558
|
+
@click="promotingId = null"
|
|
559
|
+
>
|
|
560
|
+
{{ t('initiative.curation.cancel') }}
|
|
561
|
+
</button>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
<div v-else class="flex gap-1.5">
|
|
490
565
|
<button
|
|
491
|
-
class="rounded
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
@click="savePolicy"
|
|
566
|
+
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800"
|
|
567
|
+
data-testid="initiative-followup-promote"
|
|
568
|
+
@click="startPromote(f)"
|
|
495
569
|
>
|
|
496
|
-
{{ t('initiative.curation.
|
|
570
|
+
{{ t('initiative.curation.promote') }}
|
|
497
571
|
</button>
|
|
498
572
|
<button
|
|
499
|
-
class="rounded border border-slate-700 px-
|
|
500
|
-
|
|
573
|
+
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800 disabled:opacity-50"
|
|
574
|
+
:disabled="initiatives.curating"
|
|
575
|
+
data-testid="initiative-followup-dismiss"
|
|
576
|
+
@click="dismissFollowUp(f)"
|
|
501
577
|
>
|
|
502
|
-
{{ t('initiative.curation.
|
|
578
|
+
{{ t('initiative.curation.dismiss') }}
|
|
503
579
|
</button>
|
|
504
580
|
</div>
|
|
505
581
|
</div>
|
|
506
|
-
</
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
</ul>
|
|
519
|
-
</section>
|
|
520
|
-
<section v-if="initiative.deviations?.length" class="mb-4">
|
|
521
|
-
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
522
|
-
{{ t('initiative.tracker.deviations') }}
|
|
523
|
-
</h3>
|
|
524
|
-
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
525
|
-
<li v-for="d in initiative.deviations" :key="d.id">
|
|
526
|
-
<code v-if="d.itemId" class="text-slate-400">{{ d.itemId }}</code>
|
|
527
|
-
{{ d.description }}
|
|
528
|
-
<span v-if="d.resolution" class="text-slate-400"> → {{ d.resolution }}</span>
|
|
529
|
-
</li>
|
|
530
|
-
</ul>
|
|
531
|
-
</section>
|
|
532
|
-
<section v-if="initiative.followUps?.length" class="mb-4">
|
|
533
|
-
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
534
|
-
{{ t('initiative.tracker.followUps') }}
|
|
535
|
-
</h3>
|
|
536
|
-
<ul class="flex flex-col gap-2 text-[13px] text-slate-300">
|
|
537
|
-
<li
|
|
538
|
-
v-for="f in initiative.followUps"
|
|
539
|
-
:key="f.id"
|
|
540
|
-
class="rounded-lg border border-slate-800 p-2.5"
|
|
541
|
-
:data-testid="`initiative-followup-${f.id}`"
|
|
542
|
-
>
|
|
543
|
-
<div class="flex items-start gap-2">
|
|
544
|
-
<div class="min-w-0 flex-1">
|
|
545
|
-
<span class="font-medium">{{ f.title }}</span>
|
|
546
|
-
<span v-if="f.detail" class="text-slate-400"> — {{ f.detail }}</span>
|
|
547
|
-
</div>
|
|
548
|
-
<UBadge
|
|
549
|
-
:color="INITIATIVE_FOLLOWUP_STATUS_CHIPS[f.status]"
|
|
550
|
-
variant="subtle"
|
|
551
|
-
size="sm"
|
|
552
|
-
>
|
|
553
|
-
{{ t(INITIATIVE_FOLLOWUP_STATUS_LABEL_KEYS[f.status]) }}
|
|
554
|
-
</UBadge>
|
|
555
|
-
</div>
|
|
556
|
-
<!-- Triage actions for an open follow-up (only while executing) -->
|
|
557
|
-
<div v-if="editable && f.status === 'open'" class="mt-2">
|
|
558
|
-
<div v-if="promotingId === f.id" class="flex flex-col gap-2">
|
|
559
|
-
<label class="flex items-center gap-2 text-[12px]">
|
|
560
|
-
<span class="text-slate-400">{{
|
|
561
|
-
t('initiative.curation.phaseField')
|
|
562
|
-
}}</span>
|
|
563
|
-
<select
|
|
564
|
-
v-model="promoteForm.phaseId"
|
|
565
|
-
class="flex-1 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-slate-200"
|
|
566
|
-
data-testid="initiative-promote-phase"
|
|
567
|
-
>
|
|
568
|
-
<option v-for="p in phases" :key="p.id" :value="p.id">
|
|
569
|
-
{{ p.title }}
|
|
570
|
-
</option>
|
|
571
|
-
</select>
|
|
572
|
-
</label>
|
|
573
|
-
<input
|
|
574
|
-
v-model="promoteForm.title"
|
|
575
|
-
type="text"
|
|
576
|
-
class="rounded border border-slate-700 bg-slate-950 px-2 py-1 text-[12px] text-slate-200"
|
|
577
|
-
:placeholder="t('initiative.curation.itemTitlePlaceholder')"
|
|
578
|
-
data-testid="initiative-promote-title"
|
|
579
|
-
/>
|
|
580
|
-
<div class="flex gap-2">
|
|
581
|
-
<button
|
|
582
|
-
class="rounded bg-indigo-600 px-2 py-1 text-[11px] text-white hover:bg-indigo-500 disabled:opacity-50"
|
|
583
|
-
:disabled="initiatives.curating || !promoteForm.phaseId"
|
|
584
|
-
data-testid="initiative-promote-submit"
|
|
585
|
-
@click="submitPromote(f)"
|
|
586
|
-
>
|
|
587
|
-
{{ t('initiative.curation.promoteConfirm') }}
|
|
588
|
-
</button>
|
|
589
|
-
<button
|
|
590
|
-
class="rounded border border-slate-700 px-2 py-1 text-[11px] text-slate-300 hover:bg-slate-800"
|
|
591
|
-
@click="promotingId = null"
|
|
592
|
-
>
|
|
593
|
-
{{ t('initiative.curation.cancel') }}
|
|
594
|
-
</button>
|
|
595
|
-
</div>
|
|
596
|
-
</div>
|
|
597
|
-
<div v-else class="flex gap-1.5">
|
|
598
|
-
<button
|
|
599
|
-
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800"
|
|
600
|
-
data-testid="initiative-followup-promote"
|
|
601
|
-
@click="startPromote(f)"
|
|
602
|
-
>
|
|
603
|
-
{{ t('initiative.curation.promote') }}
|
|
604
|
-
</button>
|
|
605
|
-
<button
|
|
606
|
-
class="rounded border border-slate-700 px-1.5 py-0.5 text-[10px] text-slate-300 hover:bg-slate-800 disabled:opacity-50"
|
|
607
|
-
:disabled="initiatives.curating"
|
|
608
|
-
data-testid="initiative-followup-dismiss"
|
|
609
|
-
@click="dismissFollowUp(f)"
|
|
610
|
-
>
|
|
611
|
-
{{ t('initiative.curation.dismiss') }}
|
|
612
|
-
</button>
|
|
613
|
-
</div>
|
|
614
|
-
</div>
|
|
615
|
-
</li>
|
|
616
|
-
</ul>
|
|
617
|
-
</section>
|
|
618
|
-
<section v-if="initiative.caveats?.length" class="mb-4">
|
|
619
|
-
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
620
|
-
{{ t('initiative.tracker.caveats') }}
|
|
621
|
-
</h3>
|
|
622
|
-
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
623
|
-
<li v-for="(c, i) in initiative.caveats" :key="i">{{ c }}</li>
|
|
624
|
-
</ul>
|
|
625
|
-
</section>
|
|
626
|
-
</template>
|
|
627
|
-
</div>
|
|
628
|
-
</div>
|
|
582
|
+
</li>
|
|
583
|
+
</ul>
|
|
584
|
+
</section>
|
|
585
|
+
<section v-if="initiative.caveats?.length" class="mb-4">
|
|
586
|
+
<h3 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
587
|
+
{{ t('initiative.tracker.caveats') }}
|
|
588
|
+
</h3>
|
|
589
|
+
<ul class="list-inside list-disc text-[13px] text-slate-300">
|
|
590
|
+
<li v-for="(c, i) in initiative.caveats" :key="i">{{ c }}</li>
|
|
591
|
+
</ul>
|
|
592
|
+
</section>
|
|
593
|
+
</template>
|
|
629
594
|
</div>
|
|
630
|
-
</
|
|
595
|
+
</ResultWindowShell>
|
|
631
596
|
</template>
|