@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.
@@ -9,7 +9,7 @@
9
9
  // description — is what every downstream agent step (the bug investigator, the coder)
10
10
  // consumes.
11
11
  import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
12
- import IconButton from '~/components/common/IconButton.vue'
12
+ import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
13
13
  import { parseOutputOutline } from '~/utils/agentOutput'
14
14
  import type {
15
15
  ClarityItemStatus,
@@ -303,385 +303,355 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
303
303
  </script>
304
304
 
305
305
  <template>
306
- <Teleport to="body">
307
- <div
308
- v-if="open"
309
- class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
310
- @click.self="close"
311
- >
312
- <div
313
- class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
314
- role="dialog"
315
- aria-modal="true"
316
- >
317
- <!-- header -->
318
- <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
306
+ <ResultWindowShell
307
+ :open="open"
308
+ icon="i-lucide-bug"
309
+ icon-class="bg-indigo-500/15 text-indigo-300"
310
+ :title="t('clarity.title')"
311
+ :subtitle="block?.title"
312
+ variant="centered"
313
+ width="5xl"
314
+ @close="close"
315
+ >
316
+ <template v-if="review" #header-extras>
317
+ <UBadge color="neutral" variant="subtle" size="sm">
318
+ {{ t('clarity.iteration', { current: iteration, max: maxIterations }) }}
319
+ </UBadge>
320
+ </template>
321
+
322
+ <div class="flex min-h-0 flex-1 flex-col lg:flex-row">
323
+ <!-- main column -->
324
+ <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
325
+ <p class="mb-4 text-sm text-slate-400">
326
+ <i18n-t keypath="clarity.intro" tag="span" scope="global">
327
+ <template #level>{{ block?.level ?? t('clarity.itemFallback') }}</template>
328
+ <template #answer
329
+ ><span class="text-slate-300">{{ t('clarity.introAnswer') }}</span></template
330
+ >
331
+ <template #dismiss
332
+ ><span class="text-slate-300">{{ t('clarity.introDismiss') }}</span></template
333
+ >
334
+ </i18n-t>
335
+ </p>
336
+
337
+ <!-- empty state — the reviewer runs automatically as the first pipeline
338
+ gate step, so there's nothing to do here until then -->
339
+ <div
340
+ v-if="!review && !busy && !loading"
341
+ class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
342
+ >
343
+ {{ t('clarity.empty') }}
344
+ </div>
345
+
346
+ <!-- working state (initial fetch on open, or a reviewer pass running) -->
347
+ <div
348
+ v-else-if="(busy || loading) && !review"
349
+ class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
350
+ >
351
+ <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
352
+ {{ loading && !busy ? t('clarity.loadingReview') : t('clarity.triaging') }}
353
+ </div>
354
+
355
+ <template v-else-if="review">
356
+ <!-- converged: reviewer satisfied -->
319
357
  <div
320
- class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-indigo-500/15"
358
+ v-if="incorporated"
359
+ 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"
321
360
  >
322
- <UIcon name="i-lucide-bug" class="h-5 w-5 text-indigo-300" />
323
- </div>
324
- <div class="min-w-0">
325
- <h1 class="truncate text-base font-semibold text-white">{{ t('clarity.title') }}</h1>
326
- <p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
327
- </div>
328
- <div class="ms-auto flex items-center gap-1.5">
329
- <UBadge v-if="review" color="neutral" variant="subtle" size="sm">
330
- {{ t('clarity.iteration', { current: iteration, max: maxIterations }) }}
331
- </UBadge>
332
- <IconButton
333
- icon="i-lucide-x"
334
- color="neutral"
335
- variant="ghost"
336
- size="sm"
337
- :label="t('common.close')"
338
- @click="close"
339
- />
361
+ <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
362
+ {{ t('clarity.converged') }}
340
363
  </div>
341
- </header>
342
-
343
- <div class="flex min-h-0 flex-1 flex-col lg:flex-row">
344
- <!-- main column -->
345
- <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
346
- <p class="mb-4 text-sm text-slate-400">
347
- <i18n-t keypath="clarity.intro" tag="span" scope="global">
348
- <template #level>{{ block?.level ?? t('clarity.itemFallback') }}</template>
349
- <template #answer
350
- ><span class="text-slate-300">{{ t('clarity.introAnswer') }}</span></template
351
- >
352
- <template #dismiss
353
- ><span class="text-slate-300">{{ t('clarity.introDismiss') }}</span></template
354
- >
355
- </i18n-t>
356
- </p>
357
364
 
358
- <!-- empty state the reviewer runs automatically as the first pipeline
359
- gate step, so there's nothing to do here until then -->
360
- <div
361
- v-if="!review && !busy && !loading"
362
- class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
363
- >
364
- {{ t('clarity.empty') }}
365
- </div>
365
+ <!-- iteration cap hit -->
366
+ <IterationCapPrompt
367
+ v-else-if="exceeded"
368
+ class="mb-4"
369
+ :heading="t('clarity.capHeading', { max: maxIterations })"
370
+ :detail="t('clarity.capDetail')"
371
+ :loading="acting"
372
+ @resolve="resolveExceeded"
373
+ />
374
+
375
+ <!-- working: the async cycle is running in the driver. Two distinct stages so
376
+ the human can see which of the two LLM calls is currently in progress. -->
377
+ <div
378
+ v-else-if="working"
379
+ 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"
380
+ >
381
+ <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
382
+ <span v-if="incorporating">
383
+ {{ t('clarity.incorporatingStage') }}
384
+ </span>
385
+ <span v-else>
386
+ {{ t('clarity.reReviewingStage') }}
387
+ </span>
388
+ </div>
366
389
 
367
- <!-- working state (initial fetch on open, or a reviewer pass running) -->
390
+ <!-- findings to react to -->
391
+ <div v-if="review.items.length" class="flex flex-col gap-3">
368
392
  <div
369
- v-else-if="(busy || loading) && !review"
370
- class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
393
+ v-for="item in sortedItems"
394
+ :key="item.id"
395
+ class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
396
+ :class="{ 'opacity-60': item.status === 'dismissed' }"
371
397
  >
372
- <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
373
- {{ loading && !busy ? t('clarity.loadingReview') : t('clarity.triaging') }}
374
- </div>
375
-
376
- <template v-else-if="review">
377
- <!-- converged: reviewer satisfied -->
378
- <div
379
- v-if="incorporated"
380
- 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"
381
- >
382
- <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
383
- {{ t('clarity.converged') }}
384
- </div>
385
-
386
- <!-- iteration cap hit -->
387
- <IterationCapPrompt
388
- v-else-if="exceeded"
389
- class="mb-4"
390
- :heading="t('clarity.capHeading', { max: maxIterations })"
391
- :detail="t('clarity.capDetail')"
392
- :loading="acting"
393
- @resolve="resolveExceeded"
394
- />
395
-
396
- <!-- working: the async cycle is running in the driver. Two distinct stages so
397
- the human can see which of the two LLM calls is currently in progress. -->
398
- <div
399
- v-else-if="working"
400
- 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"
401
- >
402
- <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
403
- <span v-if="incorporating">
404
- {{ t('clarity.incorporatingStage') }}
405
- </span>
406
- <span v-else>
407
- {{ t('clarity.reReviewingStage') }}
408
- </span>
409
- </div>
398
+ <div class="flex items-start gap-2">
399
+ <UIcon
400
+ :name="CATEGORY_ICON[item.category]"
401
+ class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
402
+ />
403
+ <div class="min-w-0 flex-1">
404
+ <div class="flex flex-wrap items-center gap-1.5">
405
+ <span class="text-sm font-medium text-white">{{ item.title }}</span>
406
+ <UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
407
+ {{ t(SEVERITY_LABELS[item.severity]) }}
408
+ </UBadge>
409
+ <UBadge size="xs" variant="outline" color="neutral">
410
+ {{ t(CATEGORY_LABELS[item.category]) }}
411
+ </UBadge>
412
+ <UBadge
413
+ size="xs"
414
+ variant="soft"
415
+ :color="STATUS_COLOR[item.status]"
416
+ class="ms-auto"
417
+ >
418
+ {{ t(STATUS_LABELS[item.status]) }}
419
+ </UBadge>
420
+ </div>
421
+ <p class="mt-1 whitespace-pre-line text-sm text-slate-400">
422
+ {{ item.detail }}
423
+ </p>
410
424
 
411
- <!-- findings to react to -->
412
- <div v-if="review.items.length" class="flex flex-col gap-3">
413
- <div
414
- v-for="item in sortedItems"
415
- :key="item.id"
416
- class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
417
- :class="{ 'opacity-60': item.status === 'dismissed' }"
418
- >
419
- <div class="flex items-start gap-2">
420
- <UIcon
421
- :name="CATEGORY_ICON[item.category]"
422
- class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
423
- />
424
- <div class="min-w-0 flex-1">
425
- <div class="flex flex-wrap items-center gap-1.5">
426
- <span class="text-sm font-medium text-white">{{ item.title }}</span>
427
- <UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
428
- {{ t(SEVERITY_LABELS[item.severity]) }}
429
- </UBadge>
430
- <UBadge size="xs" variant="outline" color="neutral">
431
- {{ t(CATEGORY_LABELS[item.category]) }}
432
- </UBadge>
433
- <UBadge
434
- size="xs"
435
- variant="soft"
436
- :color="STATUS_COLOR[item.status]"
437
- class="ms-auto"
438
- >
439
- {{ t(STATUS_LABELS[item.status]) }}
440
- </UBadge>
441
- </div>
442
- <p class="mt-1 whitespace-pre-line text-sm text-slate-400">
443
- {{ item.detail }}
444
- </p>
445
-
446
- <!-- recorded answer (only for non-editable findings — for editable ones
425
+ <!-- recorded answer (only for non-editable findings for editable ones
447
426
  the answer lives in the textarea below, seeded from the reply) -->
448
- <div
449
- v-if="item.reply && item.status !== 'open' && item.status !== 'answered'"
450
- class="mt-2 rounded-md border-s-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
451
- >
452
- <span class="text-[10px] uppercase tracking-wide text-slate-500">
453
- {{ t('clarity.answerLabel') }}
454
- </span>
455
- <p class="whitespace-pre-line">{{ item.reply }}</p>
456
- </div>
427
+ <div
428
+ v-if="item.reply && item.status !== 'open' && item.status !== 'answered'"
429
+ class="mt-2 rounded-md border-s-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
430
+ >
431
+ <span class="text-[10px] uppercase tracking-wide text-slate-500">
432
+ {{ t('clarity.answerLabel') }}
433
+ </span>
434
+ <p class="whitespace-pre-line">{{ item.reply }}</p>
435
+ </div>
457
436
 
458
- <!-- react: answer (relevant) or dismiss (irrelevant). The answer
437
+ <!-- react: answer (relevant) or dismiss (irrelevant). The answer
459
438
  auto-saves on blur — no explicit save button. Disabled once the
460
439
  bug report is clarified / awaiting a higher-level decision. -->
461
- <template v-if="item.status === 'open' || item.status === 'answered'">
462
- <UTextarea
463
- v-model="drafts[item.id]"
464
- :rows="2"
465
- autoresize
466
- size="sm"
467
- class="mt-2 w-full"
468
- :placeholder="t('clarity.answerPlaceholder')"
469
- :disabled="frozen"
470
- @blur="persistDraft(item)"
471
- />
472
- <div class="mt-2 flex flex-wrap items-center gap-2">
473
- <UButton
474
- color="neutral"
475
- variant="ghost"
476
- size="xs"
477
- icon="i-lucide-x"
478
- :disabled="frozen || !access.canExecuteRuns.value"
479
- :title="
480
- access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
481
- "
482
- @click="setStatus(item, 'dismissed')"
483
- >
484
- {{ t('clarity.dismissIrrelevant') }}
485
- </UButton>
486
- </div>
487
- </template>
488
-
489
- <!-- reopen a dismissed finding -->
490
- <div v-else-if="item.status === 'dismissed'" class="mt-2">
491
- <UButton
492
- color="neutral"
493
- variant="ghost"
494
- size="xs"
495
- icon="i-lucide-rotate-ccw"
496
- :disabled="frozen || !access.canExecuteRuns.value"
497
- :title="
498
- access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
499
- "
500
- @click="setStatus(item, 'open')"
501
- >
502
- {{ t('clarity.reopen') }}
503
- </UButton>
504
- </div>
440
+ <template v-if="item.status === 'open' || item.status === 'answered'">
441
+ <UTextarea
442
+ v-model="drafts[item.id]"
443
+ :rows="2"
444
+ autoresize
445
+ size="sm"
446
+ class="mt-2 w-full"
447
+ :placeholder="t('clarity.answerPlaceholder')"
448
+ :disabled="frozen"
449
+ @blur="persistDraft(item)"
450
+ />
451
+ <div class="mt-2 flex flex-wrap items-center gap-2">
452
+ <UButton
453
+ color="neutral"
454
+ variant="ghost"
455
+ size="xs"
456
+ icon="i-lucide-x"
457
+ :disabled="frozen || !access.canExecuteRuns.value"
458
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
459
+ @click="setStatus(item, 'dismissed')"
460
+ >
461
+ {{ t('clarity.dismissIrrelevant') }}
462
+ </UButton>
505
463
  </div>
464
+ </template>
465
+
466
+ <!-- reopen a dismissed finding -->
467
+ <div v-else-if="item.status === 'dismissed'" class="mt-2">
468
+ <UButton
469
+ color="neutral"
470
+ variant="ghost"
471
+ size="xs"
472
+ icon="i-lucide-rotate-ccw"
473
+ :disabled="frozen || !access.canExecuteRuns.value"
474
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
475
+ @click="setStatus(item, 'open')"
476
+ >
477
+ {{ t('clarity.reopen') }}
478
+ </UButton>
506
479
  </div>
507
480
  </div>
508
481
  </div>
509
-
510
- <!-- clarified document: the standard-format bug report -->
511
- <section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
512
- <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
513
- <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
514
- <span class="font-semibold uppercase tracking-wide">
515
- {{ incorporated ? t('clarity.docHeading') : t('clarity.docHeadingDraft') }}
516
- </span>
517
- </div>
518
- <div v-for="s in outline.sections" :key="s.id" class="mb-2">
519
- <button
520
- v-if="s.title"
521
- class="group flex w-full items-center gap-2 text-start"
522
- @click="toggle(s.id)"
523
- >
524
- <UIcon
525
- name="i-lucide-chevron-right"
526
- class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
527
- :class="collapsed[s.id] ? '' : 'rotate-90'"
528
- />
529
- <span
530
- class="font-semibold text-white"
531
- :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
532
- v-html="s.titleHtml"
533
- />
534
- </button>
535
- <div
536
- v-show="!s.title || !collapsed[s.id]"
537
- class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
538
- v-html="s.bodyHtml"
539
- />
540
- </div>
541
- </section>
542
- </template>
482
+ </div>
543
483
  </div>
544
484
 
545
- <!-- action rail: a right-hand column on wide screens, a bottom action bar below `lg`
485
+ <!-- clarified document: the standard-format bug report -->
486
+ <section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
487
+ <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
488
+ <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
489
+ <span class="font-semibold uppercase tracking-wide">
490
+ {{ incorporated ? t('clarity.docHeading') : t('clarity.docHeadingDraft') }}
491
+ </span>
492
+ </div>
493
+ <div v-for="s in outline.sections" :key="s.id" class="mb-2">
494
+ <button
495
+ v-if="s.title"
496
+ class="group flex w-full items-center gap-2 text-start"
497
+ @click="toggle(s.id)"
498
+ >
499
+ <UIcon
500
+ name="i-lucide-chevron-right"
501
+ class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
502
+ :class="collapsed[s.id] ? '' : 'rotate-90'"
503
+ />
504
+ <span
505
+ class="font-semibold text-white"
506
+ :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
507
+ v-html="s.titleHtml"
508
+ />
509
+ </button>
510
+ <div
511
+ v-show="!s.title || !collapsed[s.id]"
512
+ class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
513
+ v-html="s.bodyHtml"
514
+ />
515
+ </div>
516
+ </section>
517
+ </template>
518
+ </div>
519
+
520
+ <!-- action rail: a right-hand column on wide screens, a bottom action bar below `lg`
546
521
  (never hidden — the gate is otherwise unadvanceable on a laptop split-screen /
547
522
  tablet, UX-32). The informational stats collapse away below `lg` to keep the
548
523
  bottom bar compact; the actions themselves always show. -->
549
- <aside
550
- class="flex w-full shrink-0 flex-col border-t border-slate-800 lg:w-72 lg:border-s lg:border-t-0"
551
- >
552
- <div class="flex flex-col gap-4 px-4 py-5">
553
- <div v-if="review" class="hidden space-y-2 text-xs text-slate-400 lg:block">
554
- <div class="flex items-center justify-between">
555
- <span>{{ t('clarity.rail.findings') }}</span>
556
- <span class="text-slate-300">{{ review.items.length }}</span>
557
- </div>
558
- <div class="flex items-center justify-between">
559
- <span>{{ t('clarity.rail.open') }}</span>
560
- <span class="text-slate-300">{{ openCount }}</span>
561
- </div>
562
- <div class="flex items-center justify-between">
563
- <span>{{ t('clarity.rail.answered') }}</span>
564
- <span class="text-slate-300">{{ answeredCount }}</span>
565
- </div>
566
- <div v-if="review.model" class="flex items-center justify-between">
567
- <span>{{ t('clarity.rail.model') }}</span>
568
- <span class="truncate ps-2 text-slate-500">{{
569
- models.labelForRef(review.model) ?? review.model
570
- }}</span>
571
- </div>
572
- </div>
573
-
574
- <!-- action: ready (answer → incorporate / proceed) -->
575
- <div
576
- v-if="review && status === 'ready'"
577
- class="space-y-2 border-t border-slate-800 pt-4"
578
- >
579
- <UButton
580
- v-if="canProceed"
581
- color="primary"
582
- size="sm"
583
- block
584
- icon="i-lucide-arrow-right"
585
- :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
586
- :loading="acting"
587
- :disabled="!access.canExecuteRuns.value"
588
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
589
- @click="proceed"
590
- >
591
- {{ t('clarity.proceedNothing') }}
592
- </UButton>
593
- <UButton
594
- v-else
595
- color="primary"
596
- size="sm"
597
- block
598
- icon="i-lucide-wand-sparkles"
599
- :loading="reworking"
600
- :disabled="!canIncorporate || !access.canExecuteRuns.value"
601
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
602
- @click="incorporate()"
603
- >
604
- {{ t('clarity.incorporateAnswers') }}
605
- </UButton>
606
- <p class="text-[11px] leading-relaxed text-slate-500">
607
- <template v-if="canProceed">
608
- {{ t('clarity.hint.proceed') }}
609
- </template>
610
- <template v-else-if="canIncorporate">
611
- {{ t('clarity.hint.incorporate') }}
612
- </template>
613
- <template v-else> {{ t('clarity.hint.answerAll') }} </template>
614
- </p>
615
- </div>
524
+ <aside
525
+ class="flex w-full shrink-0 flex-col border-t border-slate-800 lg:w-72 lg:border-s lg:border-t-0"
526
+ >
527
+ <div class="flex flex-col gap-4 px-4 py-5">
528
+ <div v-if="review" class="hidden space-y-2 text-xs text-slate-400 lg:block">
529
+ <div class="flex items-center justify-between">
530
+ <span>{{ t('clarity.rail.findings') }}</span>
531
+ <span class="text-slate-300">{{ review.items.length }}</span>
532
+ </div>
533
+ <div class="flex items-center justify-between">
534
+ <span>{{ t('clarity.rail.open') }}</span>
535
+ <span class="text-slate-300">{{ openCount }}</span>
536
+ </div>
537
+ <div class="flex items-center justify-between">
538
+ <span>{{ t('clarity.rail.answered') }}</span>
539
+ <span class="text-slate-300">{{ answeredCount }}</span>
540
+ </div>
541
+ <div v-if="review.model" class="flex items-center justify-between">
542
+ <span>{{ t('clarity.rail.model') }}</span>
543
+ <span class="truncate ps-2 text-slate-500">{{
544
+ models.labelForRef(review.model) ?? review.model
545
+ }}</span>
546
+ </div>
547
+ </div>
616
548
 
617
- <!-- action: merged (inspectre-review / redo) -->
618
- <div v-if="review && merged" class="space-y-2 border-t border-slate-800 pt-4">
619
- <UButton
620
- color="primary"
621
- size="sm"
622
- block
623
- icon="i-lucide-sparkles"
624
- :loading="busy"
625
- :disabled="!access.canExecuteRuns.value"
626
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
627
- @click="reReview"
628
- >
629
- {{ busy ? t('clarity.reReviewing') : t('clarity.looksGoodReReview') }}
630
- </UButton>
631
- <UButton
632
- color="neutral"
633
- variant="soft"
634
- size="sm"
635
- block
636
- icon="i-lucide-pencil"
637
- @click="
638
- () => {
639
- showRedo = !showRedo
640
- }
641
- "
642
- >
643
- {{ t('clarity.redoIncorporation') }}
644
- </UButton>
645
- <div v-if="showRedo" class="space-y-2">
646
- <UTextarea
647
- v-model="redoComment"
648
- :rows="3"
649
- autoresize
650
- size="sm"
651
- class="w-full"
652
- :placeholder="t('clarity.redoPlaceholder')"
653
- />
654
- <UButton
655
- color="primary"
656
- variant="soft"
657
- size="xs"
658
- block
659
- icon="i-lucide-wand-sparkles"
660
- :loading="reworking"
661
- :disabled="!redoComment.trim() || !access.canExecuteRuns.value"
662
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
663
- @click="incorporate(redoComment.trim())"
664
- >
665
- {{ t('clarity.redoWithDirection') }}
666
- </UButton>
667
- </div>
668
- <p class="text-[11px] leading-relaxed text-slate-500">
669
- {{ t('clarity.redoHint') }}
670
- </p>
671
- </div>
549
+ <!-- action: ready (answerincorporate / proceed) -->
550
+ <div v-if="review && status === 'ready'" class="space-y-2 border-t border-slate-800 pt-4">
551
+ <UButton
552
+ v-if="canProceed"
553
+ color="primary"
554
+ size="sm"
555
+ block
556
+ icon="i-lucide-arrow-right"
557
+ :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
558
+ :loading="acting"
559
+ :disabled="!access.canExecuteRuns.value"
560
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
561
+ @click="proceed"
562
+ >
563
+ {{ t('clarity.proceedNothing') }}
564
+ </UButton>
565
+ <UButton
566
+ v-else
567
+ color="primary"
568
+ size="sm"
569
+ block
570
+ icon="i-lucide-wand-sparkles"
571
+ :loading="reworking"
572
+ :disabled="!canIncorporate || !access.canExecuteRuns.value"
573
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
574
+ @click="incorporate()"
575
+ >
576
+ {{ t('clarity.incorporateAnswers') }}
577
+ </UButton>
578
+ <p class="text-[11px] leading-relaxed text-slate-500">
579
+ <template v-if="canProceed">
580
+ {{ t('clarity.hint.proceed') }}
581
+ </template>
582
+ <template v-else-if="canIncorporate">
583
+ {{ t('clarity.hint.incorporate') }}
584
+ </template>
585
+ <template v-else> {{ t('clarity.hint.answerAll') }} </template>
586
+ </p>
587
+ </div>
672
588
 
673
- <div
674
- v-if="review && incorporated"
675
- class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
589
+ <!-- action: merged (inspect → re-review / redo) -->
590
+ <div v-if="review && merged" class="space-y-2 border-t border-slate-800 pt-4">
591
+ <UButton
592
+ color="primary"
593
+ size="sm"
594
+ block
595
+ icon="i-lucide-sparkles"
596
+ :loading="busy"
597
+ :disabled="!access.canExecuteRuns.value"
598
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
599
+ @click="reReview"
600
+ >
601
+ {{ busy ? t('clarity.reReviewing') : t('clarity.looksGoodReReview') }}
602
+ </UButton>
603
+ <UButton
604
+ color="neutral"
605
+ variant="soft"
606
+ size="sm"
607
+ block
608
+ icon="i-lucide-pencil"
609
+ @click="
610
+ () => {
611
+ showRedo = !showRedo
612
+ }
613
+ "
614
+ >
615
+ {{ t('clarity.redoIncorporation') }}
616
+ </UButton>
617
+ <div v-if="showRedo" class="space-y-2">
618
+ <UTextarea
619
+ v-model="redoComment"
620
+ :rows="3"
621
+ autoresize
622
+ size="sm"
623
+ class="w-full"
624
+ :placeholder="t('clarity.redoPlaceholder')"
625
+ />
626
+ <UButton
627
+ color="primary"
628
+ variant="soft"
629
+ size="xs"
630
+ block
631
+ icon="i-lucide-wand-sparkles"
632
+ :loading="reworking"
633
+ :disabled="!redoComment.trim() || !access.canExecuteRuns.value"
634
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
635
+ @click="incorporate(redoComment.trim())"
676
636
  >
677
- {{ t('clarity.incorporatedFooter') }}
678
- </div>
637
+ {{ t('clarity.redoWithDirection') }}
638
+ </UButton>
679
639
  </div>
680
- </aside>
640
+ <p class="text-[11px] leading-relaxed text-slate-500">
641
+ {{ t('clarity.redoHint') }}
642
+ </p>
643
+ </div>
644
+
645
+ <div
646
+ v-if="review && incorporated"
647
+ class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
648
+ >
649
+ {{ t('clarity.incorporatedFooter') }}
650
+ </div>
681
651
  </div>
682
- </div>
652
+ </aside>
683
653
  </div>
684
- </Teleport>
654
+ </ResultWindowShell>
685
655
  </template>
686
656
 
687
657
  <style scoped>