@cat-factory/app 0.80.0 → 0.81.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.
@@ -38,6 +38,9 @@ const reRequestNotes = ref<Record<string, string>>({})
38
38
  // Freeform "do it differently" comment when redoing a merge the human was unhappy with.
39
39
  const redoComment = ref('')
40
40
  const showRedo = ref(false)
41
+ // Human's explicit collapse choice for the whole incorporated-requirements section; null = follow
42
+ // the default (collapse it while there's still work to do so it doesn't dominate the window).
43
+ const docCollapsedOverride = ref<boolean | null>(null)
41
44
 
42
45
  // The seam contract (open/blockId/close + Escape handling + load-on-open) lives in
43
46
  // `useResultView`, so this window can't drift from the others. Declaring `onOpen` makes the
@@ -52,6 +55,7 @@ const { open, blockId, instanceId, stepIndex, close } = useResultView('requireme
52
55
  reRequestNotes.value = {}
53
56
  redoComment.value = ''
54
57
  showRedo.value = false
58
+ docCollapsedOverride.value = null
55
59
  void requirements.load(id)
56
60
  },
57
61
  })
@@ -256,6 +260,41 @@ const recommendationProgress = computed(() => {
256
260
  const ready = readyRecommendations.value.filter((r) => batchTimes.has(r.createdAt)).length
257
261
  return { ready, total: ready + generating.length }
258
262
  })
263
+ // Whether the human still has something to act on (findings to answer/dismiss or recommendations
264
+ // to decide). Drives the incorporated-document default collapse so the reference doc doesn't push
265
+ // the actionable findings/recommendations off-screen while there's still work.
266
+ const hasActionableWork = computed(() => {
267
+ if (!review.value) return false
268
+ const findingWork = review.value.items.some(
269
+ (i) => i.status === 'open' || i.status === 'answered' || i.status === 'recommend_requested',
270
+ )
271
+ return (
272
+ findingWork ||
273
+ readyRecommendations.value.length > 0 ||
274
+ generatingRecommendations.value.length > 0
275
+ )
276
+ })
277
+ // The whole incorporated-requirements section collapses as a unit (independent of the per-heading
278
+ // collapse below). Default: collapsed only in the pre-incorporation `ready`-style phase while
279
+ // there's still actionable work — so the (potentially long) reference doc stays out of the way of
280
+ // the findings the human is working through. In `merged` (inspect the draft to decide re-review vs
281
+ // redo) and `incorporated` (the settled deliverable) the document IS the thing to read, so it
282
+ // defaults expanded. The human's explicit toggle wins within a phase; a status change (below)
283
+ // clears it so a collapse from one phase doesn't leak into the next.
284
+ const docCollapsed = computed(
285
+ () =>
286
+ docCollapsedOverride.value ?? (!incorporated.value && !merged.value && hasActionableWork.value),
287
+ )
288
+ function toggleDoc() {
289
+ docCollapsedOverride.value = !docCollapsed.value
290
+ }
291
+ // Reset the manual collapse on every status transition so a collapse chosen in one phase doesn't
292
+ // persist into the next (e.g. a `ready` collapse leaking into `merged`, or surviving convergence to
293
+ // `incorporated` and hiding the final requirements) — each phase then falls back to its own default.
294
+ watch(status, () => {
295
+ docCollapsedOverride.value = null
296
+ })
297
+
259
298
  function isMarkedForRecommend(item: RequirementReviewItem): boolean {
260
299
  return markedForRecommend.value.has(item.id)
261
300
  }
@@ -751,9 +790,19 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
751
790
  </div>
752
791
  </section>
753
792
 
754
- <!-- incorporated document: the standard-format requirements -->
793
+ <!-- incorporated document: the standard-format requirements. The whole section
794
+ collapses as a unit (a long doc otherwise pushes the findings/recommendations
795
+ off-screen); the per-heading toggles below still work when it's expanded. -->
755
796
  <section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
756
- <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
797
+ <button
798
+ class="mb-3 flex w-full items-center gap-1.5 text-[11px] text-emerald-400"
799
+ @click="toggleDoc"
800
+ >
801
+ <UIcon
802
+ name="i-lucide-chevron-right"
803
+ class="h-3.5 w-3.5 shrink-0 transition-transform"
804
+ :class="docCollapsed ? '' : 'rotate-90'"
805
+ />
757
806
  <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
758
807
  <span class="font-semibold uppercase tracking-wide">
759
808
  {{
@@ -762,29 +811,31 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
762
811
  : t('requirements.incorporatedDraft')
763
812
  }}
764
813
  </span>
765
- </div>
766
- <div v-for="s in outline.sections" :key="s.id" class="mb-2">
767
- <button
768
- v-if="s.title"
769
- class="group flex w-full items-center gap-2 text-start"
770
- @click="toggle(s.id)"
771
- >
772
- <UIcon
773
- name="i-lucide-chevron-right"
774
- class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
775
- :class="collapsed[s.id] ? '' : 'rotate-90'"
776
- />
777
- <span
778
- class="font-semibold text-white"
779
- :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
780
- v-html="s.titleHtml"
814
+ </button>
815
+ <div v-show="!docCollapsed">
816
+ <div v-for="s in outline.sections" :key="s.id" class="mb-2">
817
+ <button
818
+ v-if="s.title"
819
+ class="group flex w-full items-center gap-2 text-start"
820
+ @click="toggle(s.id)"
821
+ >
822
+ <UIcon
823
+ name="i-lucide-chevron-right"
824
+ class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
825
+ :class="collapsed[s.id] ? '' : 'rotate-90'"
826
+ />
827
+ <span
828
+ class="font-semibold text-white"
829
+ :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
830
+ v-html="s.titleHtml"
831
+ />
832
+ </button>
833
+ <div
834
+ v-show="!s.title || !collapsed[s.id]"
835
+ class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
836
+ v-html="s.bodyHtml"
781
837
  />
782
- </button>
783
- <div
784
- v-show="!s.title || !collapsed[s.id]"
785
- class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
786
- v-html="s.bodyHtml"
787
- />
838
+ </div>
788
839
  </div>
789
840
  </section>
790
841
  </template>
@@ -806,12 +857,63 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
806
857
  <span>{{ t('requirements.stats.answered') }}</span>
807
858
  <span class="text-slate-300">{{ answeredCount }}</span>
808
859
  </div>
860
+ <!-- awaited recommendations — kept here (always visible) so the human can see what
861
+ the Writer is still producing / what's waiting on them even while reading the
862
+ incorporated document or acting elsewhere in the window. -->
863
+ <template v-if="generatingRecommendations.length || readyRecommendations.length">
864
+ <div
865
+ class="flex items-center gap-1.5 border-t border-slate-800/60 pt-2 text-indigo-300"
866
+ >
867
+ <UIcon name="i-lucide-wand-2" class="h-3 w-3" />
868
+ <span class="font-medium">{{ t('requirements.stats.recommendations') }}</span>
869
+ </div>
870
+ <div
871
+ v-if="generatingRecommendations.length"
872
+ class="flex items-center justify-between"
873
+ >
874
+ <span>{{ t('requirements.stats.recsGenerating') }}</span>
875
+ <span class="text-indigo-300">{{ generatingRecommendations.length }}</span>
876
+ </div>
877
+ <div v-if="readyRecommendations.length" class="flex items-center justify-between">
878
+ <span>{{ t('requirements.stats.recsToReview') }}</span>
879
+ <span class="text-indigo-300">{{ readyRecommendations.length }}</span>
880
+ </div>
881
+ </template>
809
882
  <div v-if="review.model" class="flex items-center justify-between">
810
883
  <span>{{ t('requirements.stats.model') }}</span>
811
884
  <span class="truncate ps-2 text-slate-500">{{ review.model }}</span>
812
885
  </div>
813
886
  </div>
814
887
 
888
+ <!-- Request the Requirement Writer for the marked findings. Kept OUT of the
889
+ status-scoped blocks below so it's available whenever the review is still
890
+ editable — the `ready` first pass AND a `merged` review being reworked — not
891
+ only when status is exactly `ready`. Scoped to exactly those two states (NOT a
892
+ bare `!frozen`, which would also expose it in `exceeded`, where the run is parked
893
+ on the cap decision and a fresh recommendation batch has no path to settle). -->
894
+ <div
895
+ v-if="review && markedForRecommend.size > 0 && (status === 'ready' || merged)"
896
+ class="border-t border-slate-800 pt-4"
897
+ >
898
+ <UButton
899
+ color="primary"
900
+ variant="soft"
901
+ size="sm"
902
+ block
903
+ icon="i-lucide-wand-2"
904
+ :loading="recommending"
905
+ @click="requestRecommendations"
906
+ >
907
+ {{
908
+ t(
909
+ 'requirements.actions.requestRecommendations',
910
+ { count: markedForRecommend.size },
911
+ markedForRecommend.size,
912
+ )
913
+ }}
914
+ </UButton>
915
+ </div>
916
+
815
917
  <!-- action: ready (answer → incorporate / proceed) -->
816
918
  <div
817
919
  v-if="review && status === 'ready'"
@@ -841,24 +943,6 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
841
943
  >
842
944
  {{ t('requirements.actions.incorporateAnswers') }}
843
945
  </UButton>
844
- <UButton
845
- v-if="markedForRecommend.size > 0"
846
- color="primary"
847
- variant="soft"
848
- size="sm"
849
- block
850
- icon="i-lucide-wand-2"
851
- :loading="recommending"
852
- @click="requestRecommendations"
853
- >
854
- {{
855
- t(
856
- 'requirements.actions.requestRecommendations',
857
- { count: markedForRecommend.size },
858
- markedForRecommend.size,
859
- )
860
- }}
861
- </UButton>
862
946
  <p class="text-[11px] leading-relaxed text-slate-500">
863
947
  <template v-if="canProceed">
864
948
  {{ t('requirements.help.canProceed') }}
@@ -3078,6 +3078,9 @@
3078
3078
  "findings": "Findings",
3079
3079
  "open": "Open",
3080
3080
  "answered": "Answered",
3081
+ "recommendations": "Recommendations",
3082
+ "recsGenerating": "Generating",
3083
+ "recsToReview": "To review",
3081
3084
  "model": "Model"
3082
3085
  },
3083
3086
  "actions": {
@@ -2982,6 +2982,9 @@
2982
2982
  "findings": "Hallazgos",
2983
2983
  "open": "Abiertos",
2984
2984
  "answered": "Respondidos",
2985
+ "recommendations": "Recomendaciones",
2986
+ "recsGenerating": "Generando",
2987
+ "recsToReview": "Por revisar",
2985
2988
  "model": "Modelo"
2986
2989
  },
2987
2990
  "actions": {
@@ -2982,6 +2982,9 @@
2982
2982
  "findings": "Observations",
2983
2983
  "open": "Ouvertes",
2984
2984
  "answered": "Répondues",
2985
+ "recommendations": "Recommandations",
2986
+ "recsGenerating": "En cours",
2987
+ "recsToReview": "À examiner",
2985
2988
  "model": "Modèle"
2986
2989
  },
2987
2990
  "actions": {
@@ -2993,6 +2993,9 @@
2993
2993
  "findings": "ממצאים",
2994
2994
  "open": "פתוחים",
2995
2995
  "answered": "נענו",
2996
+ "recommendations": "המלצות",
2997
+ "recsGenerating": "בתהליך יצירה",
2998
+ "recsToReview": "לבדיקה",
2996
2999
  "model": "מודל"
2997
3000
  },
2998
3001
  "actions": {
@@ -2995,6 +2995,9 @@
2995
2995
  "findings": "所見",
2996
2996
  "open": "未対応",
2997
2997
  "answered": "回答済み",
2998
+ "recommendations": "推奨事項",
2999
+ "recsGenerating": "生成中",
3000
+ "recsToReview": "確認待ち",
2998
3001
  "model": "モデル"
2999
3002
  },
3000
3003
  "actions": {
@@ -2982,6 +2982,9 @@
2982
2982
  "findings": "Uwagi",
2983
2983
  "open": "Otwarte",
2984
2984
  "answered": "Odpowiedziane",
2985
+ "recommendations": "Rekomendacje",
2986
+ "recsGenerating": "Generowanie",
2987
+ "recsToReview": "Do przejrzenia",
2985
2988
  "model": "Model"
2986
2989
  },
2987
2990
  "actions": {
@@ -2995,6 +2995,9 @@
2995
2995
  "findings": "Bulgular",
2996
2996
  "open": "Açık",
2997
2997
  "answered": "Yanıtlanan",
2998
+ "recommendations": "Öneriler",
2999
+ "recsGenerating": "Oluşturuluyor",
3000
+ "recsToReview": "İncelenecek",
2998
3001
  "model": "Model"
2999
3002
  },
3000
3003
  "actions": {
@@ -2982,6 +2982,9 @@
2982
2982
  "findings": "Зауваження",
2983
2983
  "open": "Відкриті",
2984
2984
  "answered": "З відповіддю",
2985
+ "recommendations": "Рекомендації",
2986
+ "recsGenerating": "Створення",
2987
+ "recsToReview": "На перегляд",
2985
2988
  "model": "Модель"
2986
2989
  },
2987
2990
  "actions": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.80.0",
3
+ "version": "0.81.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",