@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.
@@ -8,7 +8,7 @@
8
8
  // options or hits the iteration cap. The converged direction — not the original description — is
9
9
  // what the downstream stage (the requirements review / the architect) consumes.
10
10
  import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
11
- import IconButton from '~/components/common/IconButton.vue'
11
+ import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
12
12
  import { parseOutputOutline } from '~/utils/agentOutput'
13
13
  import type {
14
14
  BrainstormItem,
@@ -250,401 +250,365 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
250
250
  </script>
251
251
 
252
252
  <template>
253
- <Teleport to="body">
254
- <div
255
- v-if="open"
256
- class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
257
- @click.self="close"
258
- >
259
- <div
260
- 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"
261
- role="dialog"
262
- aria-modal="true"
263
- >
264
- <!-- header -->
265
- <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
266
- <div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-amber-500/15">
267
- <UIcon
268
- :name="isArchitecture ? 'i-lucide-drafting-compass' : 'i-lucide-lightbulb'"
269
- class="h-5 w-5 text-amber-300"
270
- />
271
- </div>
272
- <div class="min-w-0">
273
- <h1 class="truncate text-base font-semibold text-white">
274
- {{
275
- isArchitecture
276
- ? t('brainstorm.title.architecture')
277
- : t('brainstorm.title.requirements')
278
- }}
279
- </h1>
280
- <p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
281
- </div>
282
- <div class="ms-auto flex items-center gap-1.5">
283
- <UBadge v-if="session" color="neutral" variant="subtle" size="sm">
284
- {{ t('brainstorm.iteration', { current: iteration, max: maxIterations }) }}
285
- </UBadge>
286
- <IconButton
287
- icon="i-lucide-x"
288
- color="neutral"
289
- variant="ghost"
290
- size="sm"
291
- :label="t('common.close')"
292
- @click="close"
293
- />
253
+ <ResultWindowShell
254
+ :open="open"
255
+ :icon="isArchitecture ? 'i-lucide-drafting-compass' : 'i-lucide-lightbulb'"
256
+ icon-class="bg-amber-500/15 text-amber-300"
257
+ :title="
258
+ isArchitecture ? t('brainstorm.title.architecture') : t('brainstorm.title.requirements')
259
+ "
260
+ :subtitle="block?.title"
261
+ variant="centered"
262
+ width="5xl"
263
+ @close="close"
264
+ >
265
+ <template v-if="session" #header-extras>
266
+ <UBadge color="neutral" variant="subtle" size="sm">
267
+ {{ t('brainstorm.iteration', { current: iteration, max: maxIterations }) }}
268
+ </UBadge>
269
+ </template>
270
+
271
+ <div class="flex min-h-0 flex-1">
272
+ <!-- main column -->
273
+ <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
274
+ <p class="mb-4 text-sm text-slate-400">
275
+ <i18n-t keypath="brainstorm.intro" tag="span" scope="global">
276
+ <template #subject>{{ subjectNoun }}</template>
277
+ <template #doc>{{ docNoun }}</template>
278
+ <template #choose>
279
+ <span class="text-slate-300">{{ t('brainstorm.introChoose') }}</span>
280
+ </template>
281
+ <template #dismiss>
282
+ <span class="text-slate-300">{{ t('brainstorm.introDismiss') }}</span>
283
+ </template>
284
+ </i18n-t>
285
+ </p>
286
+
287
+ <!-- empty state -->
288
+ <div
289
+ v-if="!session && !busy && !loading"
290
+ class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
291
+ >
292
+ {{ t('brainstorm.empty') }}
293
+ </div>
294
+
295
+ <!-- working state (initial fetch on open, or an agent pass running) -->
296
+ <div
297
+ v-else-if="(busy || loading) && !session"
298
+ class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
299
+ >
300
+ <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
301
+ {{ loading && !busy ? t('brainstorm.loading') : t('brainstorm.generating') }}
302
+ </div>
303
+
304
+ <template v-else-if="session">
305
+ <!-- converged -->
306
+ <div
307
+ v-if="incorporated"
308
+ 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"
309
+ >
310
+ <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
311
+ {{ t('brainstorm.settledBanner', { doc: docNoun }) }}
294
312
  </div>
295
- </header>
296
-
297
- <div class="flex min-h-0 flex-1">
298
- <!-- main column -->
299
- <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
300
- <p class="mb-4 text-sm text-slate-400">
301
- <i18n-t keypath="brainstorm.intro" tag="span" scope="global">
302
- <template #subject>{{ subjectNoun }}</template>
303
- <template #doc>{{ docNoun }}</template>
304
- <template #choose>
305
- <span class="text-slate-300">{{ t('brainstorm.introChoose') }}</span>
306
- </template>
307
- <template #dismiss>
308
- <span class="text-slate-300">{{ t('brainstorm.introDismiss') }}</span>
309
- </template>
310
- </i18n-t>
311
- </p>
312
313
 
313
- <!-- empty state -->
314
- <div
315
- v-if="!session && !busy && !loading"
316
- class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
317
- >
318
- {{ t('brainstorm.empty') }}
319
- </div>
314
+ <!-- iteration cap hit -->
315
+ <IterationCapPrompt
316
+ v-else-if="exceeded"
317
+ class="mb-4"
318
+ :heading="t('brainstorm.exceeded.heading', { max: maxIterations })"
319
+ :detail="t('brainstorm.exceeded.detail')"
320
+ :loading="acting"
321
+ @resolve="resolveExceeded"
322
+ />
323
+
324
+ <!-- working: the async cycle is running in the driver -->
325
+ <div
326
+ v-else-if="working"
327
+ class="mb-4 flex items-center gap-2 rounded-lg border border-amber-900/60 bg-amber-950/30 p-4 text-sm text-amber-200"
328
+ >
329
+ <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
330
+ <span v-if="incorporating">
331
+ {{ t('brainstorm.working.incorporating', { doc: docNoun }) }}
332
+ </span>
333
+ <span v-else>
334
+ {{ t('brainstorm.working.reReviewing') }}
335
+ </span>
336
+ </div>
320
337
 
321
- <!-- working state (initial fetch on open, or an agent pass running) -->
338
+ <!-- options to react to -->
339
+ <div v-if="session.items.length" class="flex flex-col gap-3">
322
340
  <div
323
- v-else-if="(busy || loading) && !session"
324
- class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
341
+ v-for="item in sortedItems"
342
+ :key="item.id"
343
+ class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
344
+ :class="{ 'opacity-60': item.status === 'dismissed' }"
325
345
  >
326
- <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
327
- {{ loading && !busy ? t('brainstorm.loading') : t('brainstorm.generating') }}
328
- </div>
329
-
330
- <template v-else-if="session">
331
- <!-- converged -->
332
- <div
333
- v-if="incorporated"
334
- 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"
335
- >
336
- <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
337
- {{ t('brainstorm.settledBanner', { doc: docNoun }) }}
338
- </div>
339
-
340
- <!-- iteration cap hit -->
341
- <IterationCapPrompt
342
- v-else-if="exceeded"
343
- class="mb-4"
344
- :heading="t('brainstorm.exceeded.heading', { max: maxIterations })"
345
- :detail="t('brainstorm.exceeded.detail')"
346
- :loading="acting"
347
- @resolve="resolveExceeded"
348
- />
346
+ <div class="flex items-start gap-2">
347
+ <UIcon
348
+ :name="CATEGORY_ICON[item.category]"
349
+ class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
350
+ />
351
+ <div class="min-w-0 flex-1">
352
+ <div class="flex flex-wrap items-center gap-1.5">
353
+ <span class="text-sm font-medium text-white">{{ item.title }}</span>
354
+ <UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
355
+ {{ t(SEVERITY_LABELS[item.severity]) }}
356
+ </UBadge>
357
+ <UBadge size="xs" variant="outline" color="neutral">
358
+ {{ t(CATEGORY_LABELS[item.category]) }}
359
+ </UBadge>
360
+ <UBadge
361
+ size="xs"
362
+ variant="soft"
363
+ :color="STATUS_COLOR[item.status]"
364
+ class="ms-auto"
365
+ >
366
+ {{ t(STATUS_LABELS[item.status]) }}
367
+ </UBadge>
368
+ </div>
369
+ <p class="mt-1 whitespace-pre-line text-sm text-slate-400">
370
+ {{ item.detail }}
371
+ </p>
349
372
 
350
- <!-- working: the async cycle is running in the driver -->
351
- <div
352
- v-else-if="working"
353
- class="mb-4 flex items-center gap-2 rounded-lg border border-amber-900/60 bg-amber-950/30 p-4 text-sm text-amber-200"
354
- >
355
- <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
356
- <span v-if="incorporating">
357
- {{ t('brainstorm.working.incorporating', { doc: docNoun }) }}
358
- </span>
359
- <span v-else>
360
- {{ t('brainstorm.working.reReviewing') }}
361
- </span>
362
- </div>
373
+ <!-- recorded choice -->
374
+ <div
375
+ v-if="item.reply"
376
+ 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"
377
+ >
378
+ <span class="text-[10px] uppercase tracking-wide text-slate-500">
379
+ {{ t('brainstorm.yourChoice') }}
380
+ </span>
381
+ <p class="whitespace-pre-line">{{ item.reply }}</p>
382
+ </div>
363
383
 
364
- <!-- options to react to -->
365
- <div v-if="session.items.length" class="flex flex-col gap-3">
366
- <div
367
- v-for="item in sortedItems"
368
- :key="item.id"
369
- class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
370
- :class="{ 'opacity-60': item.status === 'dismissed' }"
371
- >
372
- <div class="flex items-start gap-2">
373
- <UIcon
374
- :name="CATEGORY_ICON[item.category]"
375
- class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
384
+ <!-- react: choose (relevant) or dismiss (irrelevant) -->
385
+ <template v-if="item.status === 'open' || item.status === 'answered'">
386
+ <UTextarea
387
+ v-model="drafts[item.id]"
388
+ :rows="2"
389
+ autoresize
390
+ size="sm"
391
+ class="mt-2 w-full"
392
+ :placeholder="
393
+ item.reply
394
+ ? t('brainstorm.replyPlaceholder.refine')
395
+ : t('brainstorm.replyPlaceholder.choose')
396
+ "
397
+ :disabled="frozen"
376
398
  />
377
- <div class="min-w-0 flex-1">
378
- <div class="flex flex-wrap items-center gap-1.5">
379
- <span class="text-sm font-medium text-white">{{ item.title }}</span>
380
- <UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
381
- {{ t(SEVERITY_LABELS[item.severity]) }}
382
- </UBadge>
383
- <UBadge size="xs" variant="outline" color="neutral">
384
- {{ t(CATEGORY_LABELS[item.category]) }}
385
- </UBadge>
386
- <UBadge
387
- size="xs"
388
- variant="soft"
389
- :color="STATUS_COLOR[item.status]"
390
- class="ms-auto"
391
- >
392
- {{ t(STATUS_LABELS[item.status]) }}
393
- </UBadge>
394
- </div>
395
- <p class="mt-1 whitespace-pre-line text-sm text-slate-400">
396
- {{ item.detail }}
397
- </p>
398
-
399
- <!-- recorded choice -->
400
- <div
401
- v-if="item.reply"
402
- 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"
399
+ <div class="mt-2 flex flex-wrap items-center gap-2">
400
+ <UButton
401
+ color="primary"
402
+ variant="soft"
403
+ size="xs"
404
+ icon="i-lucide-corner-down-left"
405
+ :disabled="
406
+ !(drafts[item.id] ?? '').trim() || frozen || !access.canExecuteRuns.value
407
+ "
408
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
409
+ @click="submitReply(item)"
410
+ >
411
+ {{ t('brainstorm.saveChoice') }}
412
+ </UButton>
413
+ <UButton
414
+ color="neutral"
415
+ variant="ghost"
416
+ size="xs"
417
+ icon="i-lucide-x"
418
+ :disabled="frozen || !access.canExecuteRuns.value"
419
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
420
+ @click="setStatus(item, 'dismissed')"
403
421
  >
404
- <span class="text-[10px] uppercase tracking-wide text-slate-500">
405
- {{ t('brainstorm.yourChoice') }}
406
- </span>
407
- <p class="whitespace-pre-line">{{ item.reply }}</p>
408
- </div>
409
-
410
- <!-- react: choose (relevant) or dismiss (irrelevant) -->
411
- <template v-if="item.status === 'open' || item.status === 'answered'">
412
- <UTextarea
413
- v-model="drafts[item.id]"
414
- :rows="2"
415
- autoresize
416
- size="sm"
417
- class="mt-2 w-full"
418
- :placeholder="
419
- item.reply
420
- ? t('brainstorm.replyPlaceholder.refine')
421
- : t('brainstorm.replyPlaceholder.choose')
422
- "
423
- :disabled="frozen"
424
- />
425
- <div class="mt-2 flex flex-wrap items-center gap-2">
426
- <UButton
427
- color="primary"
428
- variant="soft"
429
- size="xs"
430
- icon="i-lucide-corner-down-left"
431
- :disabled="
432
- !(drafts[item.id] ?? '').trim() ||
433
- frozen ||
434
- !access.canExecuteRuns.value
435
- "
436
- :title="
437
- access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
438
- "
439
- @click="submitReply(item)"
440
- >
441
- {{ t('brainstorm.saveChoice') }}
442
- </UButton>
443
- <UButton
444
- color="neutral"
445
- variant="ghost"
446
- size="xs"
447
- icon="i-lucide-x"
448
- :disabled="frozen || !access.canExecuteRuns.value"
449
- :title="
450
- access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
451
- "
452
- @click="setStatus(item, 'dismissed')"
453
- >
454
- {{ t('brainstorm.dismiss') }}
455
- </UButton>
456
- </div>
457
- </template>
458
-
459
- <!-- reopen a dismissed option -->
460
- <div v-else-if="item.status === 'dismissed'" class="mt-2">
461
- <UButton
462
- color="neutral"
463
- variant="ghost"
464
- size="xs"
465
- icon="i-lucide-rotate-ccw"
466
- :disabled="frozen || !access.canExecuteRuns.value"
467
- :title="
468
- access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
469
- "
470
- @click="setStatus(item, 'open')"
471
- >
472
- {{ t('brainstorm.reopen') }}
473
- </UButton>
474
- </div>
422
+ {{ t('brainstorm.dismiss') }}
423
+ </UButton>
475
424
  </div>
425
+ </template>
426
+
427
+ <!-- reopen a dismissed option -->
428
+ <div v-else-if="item.status === 'dismissed'" class="mt-2">
429
+ <UButton
430
+ color="neutral"
431
+ variant="ghost"
432
+ size="xs"
433
+ icon="i-lucide-rotate-ccw"
434
+ :disabled="frozen || !access.canExecuteRuns.value"
435
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
436
+ @click="setStatus(item, 'open')"
437
+ >
438
+ {{ t('brainstorm.reopen') }}
439
+ </UButton>
476
440
  </div>
477
441
  </div>
478
442
  </div>
479
-
480
- <!-- converged document: the standard-format direction -->
481
- <section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
482
- <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
483
- <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
484
- <span class="font-semibold uppercase tracking-wide">
485
- {{ incorporated ? docNoun : t('brainstorm.docDraft', { doc: docNoun }) }}
486
- </span>
487
- </div>
488
- <div v-for="s in outline.sections" :key="s.id" class="mb-2">
489
- <button
490
- v-if="s.title"
491
- class="group flex w-full items-center gap-2 text-start"
492
- @click="toggle(s.id)"
493
- >
494
- <UIcon
495
- name="i-lucide-chevron-right"
496
- class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
497
- :class="collapsed[s.id] ? '' : 'rotate-90'"
498
- />
499
- <span
500
- class="font-semibold text-white"
501
- :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
502
- v-html="s.titleHtml"
503
- />
504
- </button>
505
- <div
506
- v-show="!s.title || !collapsed[s.id]"
507
- class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
508
- v-html="s.bodyHtml"
509
- />
510
- </div>
511
- </section>
512
- </template>
443
+ </div>
513
444
  </div>
514
445
 
515
- <!-- right action rail -->
516
- <aside class="hidden w-72 shrink-0 flex-col border-s border-slate-800 lg:flex">
517
- <div class="flex flex-col gap-4 px-4 py-5">
518
- <div v-if="session" class="space-y-2 text-xs text-slate-400">
519
- <div class="flex items-center justify-between">
520
- <span>{{ t('brainstorm.rail.options') }}</span>
521
- <span class="text-slate-300">{{ session.items.length }}</span>
522
- </div>
523
- <div class="flex items-center justify-between">
524
- <span>{{ t('brainstorm.rail.open') }}</span>
525
- <span class="text-slate-300">{{ openCount }}</span>
526
- </div>
527
- <div class="flex items-center justify-between">
528
- <span>{{ t('brainstorm.rail.chosen') }}</span>
529
- <span class="text-slate-300">{{ answeredCount }}</span>
530
- </div>
531
- <div v-if="session.model" class="flex items-center justify-between">
532
- <span>{{ t('brainstorm.rail.model') }}</span>
533
- <span class="truncate ps-2 text-slate-500">{{ session.model }}</span>
534
- </div>
535
- </div>
536
-
537
- <!-- action: ready (choose → incorporate / proceed) -->
538
- <div
539
- v-if="session && status === 'ready'"
540
- class="space-y-2 border-t border-slate-800 pt-4"
446
+ <!-- converged document: the standard-format direction -->
447
+ <section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
448
+ <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
449
+ <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
450
+ <span class="font-semibold uppercase tracking-wide">
451
+ {{ incorporated ? docNoun : t('brainstorm.docDraft', { doc: docNoun }) }}
452
+ </span>
453
+ </div>
454
+ <div v-for="s in outline.sections" :key="s.id" class="mb-2">
455
+ <button
456
+ v-if="s.title"
457
+ class="group flex w-full items-center gap-2 text-start"
458
+ @click="toggle(s.id)"
541
459
  >
542
- <UButton
543
- v-if="canProceed"
544
- color="primary"
545
- size="sm"
546
- block
547
- icon="i-lucide-arrow-right"
548
- :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
549
- :loading="acting"
550
- :disabled="!access.canExecuteRuns.value"
551
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
552
- @click="proceed"
553
- >
554
- {{ t('brainstorm.proceedNothing') }}
555
- </UButton>
556
- <UButton
557
- v-else
558
- color="primary"
559
- size="sm"
560
- block
561
- icon="i-lucide-wand-sparkles"
562
- :loading="reworking"
563
- :disabled="!canIncorporate || !access.canExecuteRuns.value"
564
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
565
- @click="incorporate()"
566
- >
567
- {{ t('brainstorm.incorporateChoices') }}
568
- </UButton>
569
- <p class="text-[11px] leading-relaxed text-slate-500">
570
- <template v-if="canProceed">
571
- {{ t('brainstorm.hint.allDismissed') }}
572
- </template>
573
- <template v-else-if="canIncorporate">
574
- {{ t('brainstorm.hint.incorporate', { doc: docNoun }) }}
575
- </template>
576
- <template v-else> {{ t('brainstorm.hint.chooseAll') }} </template>
577
- </p>
578
- </div>
460
+ <UIcon
461
+ name="i-lucide-chevron-right"
462
+ class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
463
+ :class="collapsed[s.id] ? '' : 'rotate-90'"
464
+ />
465
+ <span
466
+ class="font-semibold text-white"
467
+ :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
468
+ v-html="s.titleHtml"
469
+ />
470
+ </button>
471
+ <div
472
+ v-show="!s.title || !collapsed[s.id]"
473
+ class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
474
+ v-html="s.bodyHtml"
475
+ />
476
+ </div>
477
+ </section>
478
+ </template>
479
+ </div>
579
480
 
580
- <!-- action: merged (inspect → re-run / redo) -->
581
- <div v-if="session && merged" class="space-y-2 border-t border-slate-800 pt-4">
582
- <UButton
583
- color="primary"
584
- size="sm"
585
- block
586
- icon="i-lucide-sparkles"
587
- :loading="busy"
588
- :disabled="!access.canExecuteRuns.value"
589
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
590
- @click="reReview"
591
- >
592
- {{ busy ? t('brainstorm.reRunning') : t('brainstorm.reRun') }}
593
- </UButton>
594
- <UButton
595
- color="neutral"
596
- variant="soft"
597
- size="sm"
598
- block
599
- icon="i-lucide-pencil"
600
- @click="
601
- () => {
602
- showRedo = !showRedo
603
- }
604
- "
605
- >
606
- {{ t('brainstorm.redoIncorporation') }}
607
- </UButton>
608
- <div v-if="showRedo" class="space-y-2">
609
- <UTextarea
610
- v-model="redoComment"
611
- :rows="3"
612
- autoresize
613
- size="sm"
614
- class="w-full"
615
- :placeholder="t('brainstorm.redoPlaceholder')"
616
- />
617
- <UButton
618
- color="primary"
619
- variant="soft"
620
- size="xs"
621
- block
622
- icon="i-lucide-wand-sparkles"
623
- :loading="reworking"
624
- :disabled="!redoComment.trim() || !access.canExecuteRuns.value"
625
- :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
626
- @click="incorporate(redoComment.trim())"
627
- >
628
- {{ t('brainstorm.redoWithDirection') }}
629
- </UButton>
630
- </div>
631
- <p class="text-[11px] leading-relaxed text-slate-500">
632
- {{ t('brainstorm.mergedHint') }}
633
- </p>
634
- </div>
481
+ <!-- right action rail -->
482
+ <aside class="hidden w-72 shrink-0 flex-col border-s border-slate-800 lg:flex">
483
+ <div class="flex flex-col gap-4 px-4 py-5">
484
+ <div v-if="session" class="space-y-2 text-xs text-slate-400">
485
+ <div class="flex items-center justify-between">
486
+ <span>{{ t('brainstorm.rail.options') }}</span>
487
+ <span class="text-slate-300">{{ session.items.length }}</span>
488
+ </div>
489
+ <div class="flex items-center justify-between">
490
+ <span>{{ t('brainstorm.rail.open') }}</span>
491
+ <span class="text-slate-300">{{ openCount }}</span>
492
+ </div>
493
+ <div class="flex items-center justify-between">
494
+ <span>{{ t('brainstorm.rail.chosen') }}</span>
495
+ <span class="text-slate-300">{{ answeredCount }}</span>
496
+ </div>
497
+ <div v-if="session.model" class="flex items-center justify-between">
498
+ <span>{{ t('brainstorm.rail.model') }}</span>
499
+ <span class="truncate ps-2 text-slate-500">{{ session.model }}</span>
500
+ </div>
501
+ </div>
635
502
 
636
- <div
637
- v-if="session && incorporated"
638
- class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
503
+ <!-- action: ready (choose → incorporate / proceed) -->
504
+ <div
505
+ v-if="session && status === 'ready'"
506
+ class="space-y-2 border-t border-slate-800 pt-4"
507
+ >
508
+ <UButton
509
+ v-if="canProceed"
510
+ color="primary"
511
+ size="sm"
512
+ block
513
+ icon="i-lucide-arrow-right"
514
+ :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
515
+ :loading="acting"
516
+ :disabled="!access.canExecuteRuns.value"
517
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
518
+ @click="proceed"
519
+ >
520
+ {{ t('brainstorm.proceedNothing') }}
521
+ </UButton>
522
+ <UButton
523
+ v-else
524
+ color="primary"
525
+ size="sm"
526
+ block
527
+ icon="i-lucide-wand-sparkles"
528
+ :loading="reworking"
529
+ :disabled="!canIncorporate || !access.canExecuteRuns.value"
530
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
531
+ @click="incorporate()"
532
+ >
533
+ {{ t('brainstorm.incorporateChoices') }}
534
+ </UButton>
535
+ <p class="text-[11px] leading-relaxed text-slate-500">
536
+ <template v-if="canProceed">
537
+ {{ t('brainstorm.hint.allDismissed') }}
538
+ </template>
539
+ <template v-else-if="canIncorporate">
540
+ {{ t('brainstorm.hint.incorporate', { doc: docNoun }) }}
541
+ </template>
542
+ <template v-else> {{ t('brainstorm.hint.chooseAll') }} </template>
543
+ </p>
544
+ </div>
545
+
546
+ <!-- action: merged (inspect → re-run / redo) -->
547
+ <div v-if="session && merged" class="space-y-2 border-t border-slate-800 pt-4">
548
+ <UButton
549
+ color="primary"
550
+ size="sm"
551
+ block
552
+ icon="i-lucide-sparkles"
553
+ :loading="busy"
554
+ :disabled="!access.canExecuteRuns.value"
555
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
556
+ @click="reReview"
557
+ >
558
+ {{ busy ? t('brainstorm.reRunning') : t('brainstorm.reRun') }}
559
+ </UButton>
560
+ <UButton
561
+ color="neutral"
562
+ variant="soft"
563
+ size="sm"
564
+ block
565
+ icon="i-lucide-pencil"
566
+ @click="
567
+ () => {
568
+ showRedo = !showRedo
569
+ }
570
+ "
571
+ >
572
+ {{ t('brainstorm.redoIncorporation') }}
573
+ </UButton>
574
+ <div v-if="showRedo" class="space-y-2">
575
+ <UTextarea
576
+ v-model="redoComment"
577
+ :rows="3"
578
+ autoresize
579
+ size="sm"
580
+ class="w-full"
581
+ :placeholder="t('brainstorm.redoPlaceholder')"
582
+ />
583
+ <UButton
584
+ color="primary"
585
+ variant="soft"
586
+ size="xs"
587
+ block
588
+ icon="i-lucide-wand-sparkles"
589
+ :loading="reworking"
590
+ :disabled="!redoComment.trim() || !access.canExecuteRuns.value"
591
+ :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
592
+ @click="incorporate(redoComment.trim())"
639
593
  >
640
- {{ t('brainstorm.incorporatedFooter') }}
641
- </div>
594
+ {{ t('brainstorm.redoWithDirection') }}
595
+ </UButton>
642
596
  </div>
643
- </aside>
597
+ <p class="text-[11px] leading-relaxed text-slate-500">
598
+ {{ t('brainstorm.mergedHint') }}
599
+ </p>
600
+ </div>
601
+
602
+ <div
603
+ v-if="session && incorporated"
604
+ class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
605
+ >
606
+ {{ t('brainstorm.incorporatedFooter') }}
607
+ </div>
644
608
  </div>
645
- </div>
609
+ </aside>
646
610
  </div>
647
- </Teleport>
611
+ </ResultWindowShell>
648
612
  </template>
649
613
 
650
614
  <style scoped>