@cat-factory/app 0.83.1 → 0.84.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.
@@ -13,6 +13,7 @@ import {
13
13
  INITIATIVE_ITEM_STATUS_CHIPS,
14
14
  INITIATIVE_ITEM_STATUS_LABEL_KEYS,
15
15
  INITIATIVE_STATUS_LABEL_KEYS,
16
+ initiativeProgress,
16
17
  } from '~/utils/initiative'
17
18
 
18
19
  const board = useBoardStore()
@@ -31,6 +32,13 @@ function itemsOf(phaseId: string): InitiativeItem[] {
31
32
  return (initiative.value?.items ?? []).filter((i) => i.phaseId === phaseId)
32
33
  }
33
34
 
35
+ const progress = computed(() => initiativeProgress(initiative.value?.items))
36
+ const progressPct = computed(() =>
37
+ progress.value && progress.value.total > 0
38
+ ? Math.round((progress.value.settled / progress.value.total) * 100)
39
+ : 0,
40
+ )
41
+
34
42
  const policyRules = computed(() => initiative.value?.policy?.rules ?? [])
35
43
  function ruleAxes(rule: { minComplexity?: number; minRisk?: number; minImpact?: number }): string {
36
44
  const axes = [
@@ -74,6 +82,17 @@ function ruleAxes(rule: { minComplexity?: number; minRisk?: number; minImpact?:
74
82
  {{ t('initiative.tracker.subtitle') }}
75
83
  </p>
76
84
  </div>
85
+ <div v-if="progress" class="flex items-center gap-2" data-testid="initiative-progress">
86
+ <div class="h-1.5 w-24 overflow-hidden rounded-full bg-slate-800">
87
+ <div
88
+ class="h-full rounded-full bg-emerald-500 transition-[width] duration-500"
89
+ :style="{ width: `${progressPct}%` }"
90
+ />
91
+ </div>
92
+ <span class="text-[11px] tabular-nums text-slate-400">
93
+ {{ t('initiative.card.progress', { done: progress.settled, total: progress.total }) }}
94
+ </span>
95
+ </div>
77
96
  <UBadge v-if="initiative" color="primary" variant="subtle" size="sm">
78
97
  {{ t(INITIATIVE_STATUS_LABEL_KEYS[initiative.status]) }}
79
98
  </UBadge>
@@ -60,6 +60,9 @@ const META: Record<Notification['type'], { icon: string; color: Accent }> = {
60
60
  // Clicking the title opens the Follow-up companion window for the run (see `reveal`); "act"
61
61
  // just marks it read (items are decided in that window — file / send back / answer — not here).
62
62
  followup_pending: { icon: 'i-lucide-compass', color: 'warning' },
63
+ // The initiative loop needs attention (a blocked task, or completion). Clicking the title
64
+ // opens the initiative tracker window; "act" just marks it read.
65
+ initiative: { icon: 'i-lucide-milestone', color: 'primary' },
63
66
  }
64
67
 
65
68
  // Per-type primary-action label. An exhaustive Record keyed off the notification
@@ -78,6 +81,7 @@ const ACTION_KEYS: Record<Notification['type'], string> = {
78
81
  visual_confirmation_ready: 'layout.notifications.action.visual_confirmation_ready',
79
82
  human_review: 'layout.notifications.action.human_review',
80
83
  followup_pending: 'layout.notifications.action.followup_pending',
84
+ initiative: 'layout.notifications.action.initiative',
81
85
  }
82
86
 
83
87
  /** The localized primary-action label for a notification (te()-guarded against a
@@ -147,6 +151,7 @@ function reveal(n: Notification) {
147
151
  else if (n.type === 'visual_confirmation_ready') revealVisualConfirm(n)
148
152
  else if (n.type === 'human_review') revealHumanReview(n)
149
153
  else if (n.type === 'followup_pending') revealFollowUps(n)
154
+ else if (n.type === 'initiative') ui.openInitiativeTracker(n.blockId)
150
155
  else ui.select(n.blockId)
151
156
  }
152
157
 
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  // Inspector body for an `initiative`-level block: the entity's status + goal, the
3
3
  // "Run planning" control (pinned to the Initiative Planning pipeline — the engine
4
- // refuses any other on this block), and the tracker window opener. Read-only in
5
- // this slice; plan/policy editing lands with the execution loop.
4
+ // refuses any other on this block), the execution-loop controls (pause / resume /
5
+ // cancel once executing), and the tracker window opener. Plan/policy editing lands
6
+ // with slice 4.
6
7
  import type { Block, InitiativeStatus } from '~/types/domain'
7
8
  import { INITIATIVE_STATUS_LABEL_KEYS, initiativeProgress } from '~/utils/initiative'
8
9
 
@@ -40,6 +41,13 @@ function openPlanning() {
40
41
  }
41
42
 
42
43
  const progress = computed(() => initiativeProgress(initiative.value?.items))
44
+
45
+ // Execution-loop controls appear once planning is done (the loop owns the block).
46
+ const isExecuting = computed(() => status.value === 'executing')
47
+ const isPaused = computed(() => status.value === 'paused')
48
+ function control(action: 'pause' | 'resume' | 'cancel') {
49
+ void initiatives.control(props.block.id, action)
50
+ }
43
51
  </script>
44
52
 
45
53
  <template>
@@ -92,6 +100,45 @@ const progress = computed(() => initiativeProgress(initiative.value?.items))
92
100
  </UButton>
93
101
  </div>
94
102
 
103
+ <!-- Execution-loop controls (slice 3): pause / resume / cancel an executing initiative. -->
104
+ <div v-if="isExecuting || isPaused" class="flex flex-wrap items-center gap-2">
105
+ <UButton
106
+ v-if="isExecuting"
107
+ data-testid="initiative-pause"
108
+ color="warning"
109
+ variant="soft"
110
+ size="sm"
111
+ icon="i-lucide-pause"
112
+ :loading="initiatives.controlling"
113
+ @click="control('pause')"
114
+ >
115
+ {{ t('initiative.inspector.pause') }}
116
+ </UButton>
117
+ <UButton
118
+ v-if="isPaused"
119
+ data-testid="initiative-resume"
120
+ color="primary"
121
+ variant="soft"
122
+ size="sm"
123
+ icon="i-lucide-play"
124
+ :loading="initiatives.controlling"
125
+ @click="control('resume')"
126
+ >
127
+ {{ t('initiative.inspector.resume') }}
128
+ </UButton>
129
+ <UButton
130
+ data-testid="initiative-cancel"
131
+ color="error"
132
+ variant="soft"
133
+ size="sm"
134
+ icon="i-lucide-square"
135
+ :loading="initiatives.controlling"
136
+ @click="control('cancel')"
137
+ >
138
+ {{ t('initiative.inspector.cancel') }}
139
+ </UButton>
140
+ </div>
141
+
95
142
  <p class="text-[11px] text-slate-500">
96
143
  {{ t('initiative.inspector.hint') }}
97
144
  </p>
@@ -31,6 +31,7 @@ const ROUTABLE = computed<{ type: NotificationType; label: string }[]>(() => [
31
31
  { type: 'release_regression', label: t('slack.routable.release_regression') },
32
32
  { type: 'human_test_ready', label: t('slack.routable.human_test_ready') },
33
33
  { type: 'visual_confirmation_ready', label: t('slack.routable.visual_confirmation_ready') },
34
+ { type: 'initiative', label: t('slack.routable.initiative') },
34
35
  ])
35
36
 
36
37
  /** Notification-role options for a mapped member (drives who gets @-mentioned). */
@@ -54,6 +55,7 @@ const routes = reactive<Record<NotificationType, SlackRoute>>({
54
55
  visual_confirmation_ready: { enabled: false, channel: '' },
55
56
  human_review: { enabled: false, channel: '' },
56
57
  followup_pending: { enabled: false, channel: '' },
58
+ initiative: { enabled: false, channel: '' },
57
59
  })
58
60
  const mentionsEnabled = ref(false)
59
61
  const mapping = ref<SlackMemberMappingEntry[]>([])
@@ -1,11 +1,14 @@
1
1
  import {
2
2
  answerInitiativeQuestionContract,
3
+ cancelInitiativeContract,
3
4
  continueInitiativePlanningContract,
4
5
  createInitiativeContract,
5
6
  getInitiativeByBlockContract,
6
7
  getInitiativeContract,
7
8
  listInitiativesContract,
9
+ pauseInitiativeContract,
8
10
  proceedInitiativePlanningContract,
11
+ resumeInitiativeContract,
9
12
  } from '@cat-factory/contracts'
10
13
  import type { ApiContext } from './context'
11
14
 
@@ -53,5 +56,15 @@ export function initiativeApi({ send, ws }: ApiContext) {
53
56
  pathPrefix: ws(workspaceId),
54
57
  pathParams: { blockId },
55
58
  }),
59
+
60
+ // Execution-loop controls (slice 3): pause / resume / cancel an executing initiative.
61
+ pauseInitiative: (workspaceId: string, blockId: string) =>
62
+ send(pauseInitiativeContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
63
+
64
+ resumeInitiative: (workspaceId: string, blockId: string) =>
65
+ send(resumeInitiativeContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
66
+
67
+ cancelInitiative: (workspaceId: string, blockId: string) =>
68
+ send(cancelInitiativeContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
56
69
  }
57
70
  }
@@ -132,6 +132,28 @@ export const useInitiativesStore = defineStore('initiatives', () => {
132
132
  }
133
133
  }
134
134
 
135
+ /** True while a loop control (pause/resume/cancel) is in flight. */
136
+ const controlling = ref(false)
137
+
138
+ /** Pause / resume / cancel an executing initiative's loop. Applies the returned entity. */
139
+ async function control(blockId: string, action: 'pause' | 'resume' | 'cancel') {
140
+ if (!workspace.workspaceId) throw new Error('No active workspace')
141
+ controlling.value = true
142
+ try {
143
+ const call =
144
+ action === 'pause'
145
+ ? api.pauseInitiative
146
+ : action === 'resume'
147
+ ? api.resumeInitiative
148
+ : api.cancelInitiative
149
+ const updated = await call(workspace.workspaceId, blockId)
150
+ if (updated) upsert(updated)
151
+ return updated
152
+ } finally {
153
+ controlling.value = false
154
+ }
155
+ }
156
+
135
157
  function reset() {
136
158
  byBlock.value = {}
137
159
  }
@@ -142,6 +164,7 @@ export const useInitiativesStore = defineStore('initiatives', () => {
142
164
  all,
143
165
  creating,
144
166
  resuming,
167
+ controlling,
145
168
  forBlock,
146
169
  hydrate,
147
170
  upsert,
@@ -150,6 +173,7 @@ export const useInitiativesStore = defineStore('initiatives', () => {
150
173
  answerQuestion,
151
174
  continuePlanning,
152
175
  proceedPlanning,
176
+ control,
153
177
  reset,
154
178
  }
155
179
  })
@@ -1346,6 +1346,7 @@
1346
1346
  "visual_confirmation_ready": "Mark read",
1347
1347
  "human_review": "Mark read",
1348
1348
  "followup_pending": "Mark read",
1349
+ "initiative": "Mark read",
1349
1350
  "markRead": "Mark read"
1350
1351
  }
1351
1352
  },
@@ -2623,7 +2624,8 @@
2623
2624
  "clarity_review": "Clarity review",
2624
2625
  "release_regression": "Release regression",
2625
2626
  "human_test_ready": "Ready for human testing",
2626
- "visual_confirmation_ready": "Ready for visual confirmation"
2627
+ "visual_confirmation_ready": "Ready for visual confirmation",
2628
+ "initiative": "Initiative updates"
2627
2629
  },
2628
2630
  "role": {
2629
2631
  "engineering": "Engineering",
@@ -4140,6 +4142,9 @@
4140
4142
  "inspector": {
4141
4143
  "runPlanning": "Run planning",
4142
4144
  "answerPlanning": "Answer planning questions",
4145
+ "pause": "Pause",
4146
+ "resume": "Resume",
4147
+ "cancel": "Cancel initiative",
4143
4148
  "hint": "The planning pipeline explores the codebase, drafts the multi-phase plan for approval, then commits the tracker document to the repository."
4144
4149
  },
4145
4150
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "Marcar como leída",
1295
1295
  "human_review": "Marcar como leída",
1296
1296
  "followup_pending": "Marcar como leída",
1297
+ "initiative": "Marcar como leída",
1297
1298
  "markRead": "Marcar como leída"
1298
1299
  },
1299
1300
  "toast": {
@@ -2549,7 +2550,8 @@
2549
2550
  "clarity_review": "Revision de claridad",
2550
2551
  "release_regression": "Regresion de version",
2551
2552
  "human_test_ready": "Listo para pruebas humanas",
2552
- "visual_confirmation_ready": "Listo para confirmacion visual"
2553
+ "visual_confirmation_ready": "Listo para confirmacion visual",
2554
+ "initiative": "Actualizaciones de la iniciativa"
2553
2555
  },
2554
2556
  "role": {
2555
2557
  "engineering": "Ingenieria",
@@ -4021,6 +4023,9 @@
4021
4023
  "inspector": {
4022
4024
  "runPlanning": "Ejecutar planificacion",
4023
4025
  "answerPlanning": "Responder preguntas de planificacion",
4026
+ "pause": "Pausar",
4027
+ "resume": "Reanudar",
4028
+ "cancel": "Cancelar iniciativa",
4024
4029
  "hint": "El pipeline de planificacion explora el codigo, redacta el plan multifase para su aprobacion y luego confirma el documento de seguimiento en el repositorio."
4025
4030
  },
4026
4031
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "Marquer comme lu",
1295
1295
  "human_review": "Marquer comme lu",
1296
1296
  "followup_pending": "Marquer comme lu",
1297
+ "initiative": "Marquer comme lu",
1297
1298
  "markRead": "Marquer comme lu"
1298
1299
  },
1299
1300
  "toast": {
@@ -2549,7 +2550,8 @@
2549
2550
  "clarity_review": "Revue de clarte",
2550
2551
  "release_regression": "Regression de version",
2551
2552
  "human_test_ready": "Pret pour les tests humains",
2552
- "visual_confirmation_ready": "Pret pour la confirmation visuelle"
2553
+ "visual_confirmation_ready": "Pret pour la confirmation visuelle",
2554
+ "initiative": "Mises a jour de l'initiative"
2553
2555
  },
2554
2556
  "role": {
2555
2557
  "engineering": "Ingenierie",
@@ -4021,6 +4023,9 @@
4021
4023
  "inspector": {
4022
4024
  "runPlanning": "Lancer la planification",
4023
4025
  "answerPlanning": "Repondre aux questions de planification",
4026
+ "pause": "Mettre en pause",
4027
+ "resume": "Reprendre",
4028
+ "cancel": "Annuler l'initiative",
4024
4029
  "hint": "Le pipeline de planification explore le code, redige le plan multiphase pour approbation, puis valide le document de suivi dans le depot."
4025
4030
  },
4026
4031
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "סמן כנקרא",
1295
1295
  "human_review": "סמן כנקרא",
1296
1296
  "followup_pending": "סמן כנקרא",
1297
+ "initiative": "סמן כנקרא",
1297
1298
  "markRead": "סמן כנקרא"
1298
1299
  },
1299
1300
  "toast": {
@@ -2560,7 +2561,8 @@
2560
2561
  "clarity_review": "סקירת בהירות",
2561
2562
  "release_regression": "רגרסיית שחרור",
2562
2563
  "human_test_ready": "מוכן לבדיקה אנושית",
2563
- "visual_confirmation_ready": "מוכן לאישור חזותי"
2564
+ "visual_confirmation_ready": "מוכן לאישור חזותי",
2565
+ "initiative": "עדכוני יוזמה"
2564
2566
  },
2565
2567
  "role": {
2566
2568
  "engineering": "הנדסה",
@@ -4032,6 +4034,9 @@
4032
4034
  "inspector": {
4033
4035
  "runPlanning": "הרצת תכנון",
4034
4036
  "answerPlanning": "מענה על שאלות התכנון",
4037
+ "pause": "השהה",
4038
+ "resume": "המשך",
4039
+ "cancel": "ביטול היוזמה",
4035
4040
  "hint": "צינור התכנון חוקר את הקוד, מנסח את התוכנית הרב-שלבית לאישור, ואז שומר את מסמך המעקב במאגר."
4036
4041
  },
4037
4042
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "既読にする",
1295
1295
  "human_review": "既読にする",
1296
1296
  "followup_pending": "既読にする",
1297
+ "initiative": "既読にする",
1297
1298
  "markRead": "既読にする"
1298
1299
  },
1299
1300
  "toast": {
@@ -2562,7 +2563,8 @@
2562
2563
  "clarity_review": "明確性レビュー",
2563
2564
  "release_regression": "リリースリグレッション",
2564
2565
  "human_test_ready": "人手テスト準備完了",
2565
- "visual_confirmation_ready": "ビジュアル確認準備完了"
2566
+ "visual_confirmation_ready": "ビジュアル確認準備完了",
2567
+ "initiative": "イニシアチブの更新"
2566
2568
  },
2567
2569
  "role": {
2568
2570
  "engineering": "エンジニアリング",
@@ -4034,6 +4036,9 @@
4034
4036
  "inspector": {
4035
4037
  "runPlanning": "計画を実行",
4036
4038
  "answerPlanning": "計画の質問に回答",
4039
+ "pause": "一時停止",
4040
+ "resume": "再開",
4041
+ "cancel": "イニシアチブをキャンセル",
4037
4042
  "hint": "計画パイプラインはコードベースを調査し、承認用の複数フェーズ計画を起草し、その後トラッカー文書をリポジトリにコミットします。"
4038
4043
  },
4039
4044
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "Oznacz jako przeczytane",
1295
1295
  "human_review": "Oznacz jako przeczytane",
1296
1296
  "followup_pending": "Oznacz jako przeczytane",
1297
+ "initiative": "Oznacz jako przeczytane",
1297
1298
  "markRead": "Oznacz jako przeczytane"
1298
1299
  },
1299
1300
  "toast": {
@@ -2549,7 +2550,8 @@
2549
2550
  "clarity_review": "Przeglad klarownosci",
2550
2551
  "release_regression": "Regresja wydania",
2551
2552
  "human_test_ready": "Gotowe do testow przez czlowieka",
2552
- "visual_confirmation_ready": "Gotowe do potwierdzenia wizualnego"
2553
+ "visual_confirmation_ready": "Gotowe do potwierdzenia wizualnego",
2554
+ "initiative": "Aktualizacje inicjatywy"
2553
2555
  },
2554
2556
  "role": {
2555
2557
  "engineering": "Inzynieria",
@@ -4021,6 +4023,9 @@
4021
4023
  "inspector": {
4022
4024
  "runPlanning": "Uruchom planowanie",
4023
4025
  "answerPlanning": "Odpowiedz na pytania planowania",
4026
+ "pause": "Wstrzymaj",
4027
+ "resume": "Wznow",
4028
+ "cancel": "Anuluj inicjatywe",
4024
4029
  "hint": "Pipeline planowania bada kod, przygotowuje wielofazowy plan do zatwierdzenia, a nastepnie zapisuje dokument trackera w repozytorium."
4025
4030
  },
4026
4031
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "Okundu işaretle",
1295
1295
  "human_review": "Okundu işaretle",
1296
1296
  "followup_pending": "Okundu işaretle",
1297
+ "initiative": "Okundu işaretle",
1297
1298
  "markRead": "Okundu işaretle"
1298
1299
  },
1299
1300
  "toast": {
@@ -2562,7 +2563,8 @@
2562
2563
  "clarity_review": "Netlik incelemesi",
2563
2564
  "release_regression": "Sürüm gerilemesi",
2564
2565
  "human_test_ready": "İnsan testine hazır",
2565
- "visual_confirmation_ready": "Görsel onaya hazır"
2566
+ "visual_confirmation_ready": "Görsel onaya hazır",
2567
+ "initiative": "Girisim guncellemeleri"
2566
2568
  },
2567
2569
  "role": {
2568
2570
  "engineering": "Mühendislik",
@@ -4034,6 +4036,9 @@
4034
4036
  "inspector": {
4035
4037
  "runPlanning": "Planlamayi calistir",
4036
4038
  "answerPlanning": "Planlama sorularini yanitla",
4039
+ "pause": "Duraklat",
4040
+ "resume": "Devam et",
4041
+ "cancel": "Girisimi iptal et",
4037
4042
  "hint": "Planlama hatti kod tabanini inceler, onay icin cok asamali plani hazirlar ve ardindan izleyici belgesini depoya kaydeder."
4038
4043
  },
4039
4044
  "planning": {
@@ -1294,6 +1294,7 @@
1294
1294
  "visual_confirmation_ready": "Позначити прочитаним",
1295
1295
  "human_review": "Позначити прочитаним",
1296
1296
  "followup_pending": "Позначити прочитаним",
1297
+ "initiative": "Позначити прочитаним",
1297
1298
  "markRead": "Позначити прочитаним"
1298
1299
  },
1299
1300
  "toast": {
@@ -2549,7 +2550,8 @@
2549
2550
  "clarity_review": "Перевірка ясності",
2550
2551
  "release_regression": "Регресія випуску",
2551
2552
  "human_test_ready": "Готово до тестування людиною",
2552
- "visual_confirmation_ready": "Готово до візуального підтвердження"
2553
+ "visual_confirmation_ready": "Готово до візуального підтвердження",
2554
+ "initiative": "Оновлення ініціативи"
2553
2555
  },
2554
2556
  "role": {
2555
2557
  "engineering": "Інженерія",
@@ -4021,6 +4023,9 @@
4021
4023
  "inspector": {
4022
4024
  "runPlanning": "Запустити планування",
4023
4025
  "answerPlanning": "Відповісти на питання планування",
4026
+ "pause": "Призупинити",
4027
+ "resume": "Відновити",
4028
+ "cancel": "Скасувати ініціативу",
4024
4029
  "hint": "Пайплайн планування досліджує кодову базу, готує багатофазний план на затвердження, а потім комітить документ трекера до репозиторію."
4025
4030
  },
4026
4031
  "planning": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.83.1",
3
+ "version": "0.84.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",
@@ -34,7 +34,7 @@
34
34
  "valibot": "^1.4.2",
35
35
  "vue": "^3.5.39",
36
36
  "wretch": "^3.0.9",
37
- "@cat-factory/contracts": "0.90.0"
37
+ "@cat-factory/contracts": "0.91.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",