@cat-factory/app 0.284.0 → 0.285.1

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.
@@ -10,7 +10,7 @@ import { useResultView } from '~/composables/useResultView'
10
10
  import { useExecutionStore } from '~/stores/execution'
11
11
  import { useBoardStore } from '~/stores/board'
12
12
  import { useFollowUpsStore } from '~/stores/followUps'
13
- import type { FollowUpItem } from '~/types/execution'
13
+ import type { FollowUpItem, FollowUpResolution } from '~/types/execution'
14
14
  import { FOLLOW_UP_COMPANION_META } from '~/utils/catalog'
15
15
  import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
16
16
 
@@ -56,14 +56,23 @@ async function onQueue(item: FollowUpItem) {
56
56
  const id = execId()
57
57
  if (id) await followUps.queueItem(id, item.id).catch(() => {})
58
58
  }
59
- async function onAnswer(item: FollowUpItem) {
59
+ /**
60
+ * Answer a question, either way.
61
+ *
62
+ * `answered` buys the Coder another pass to apply what you said. `closed` records the reply as a
63
+ * RULING: the item is decided and the gate clears exactly the same, but no pass is spent and the
64
+ * Coder is told the topic is settled so it stops re-raising it. Two buttons rather than one with a
65
+ * toggle, because the choice is not a preference about this window: it is what the answer IS, and
66
+ * the person typing it is the only one who knows.
67
+ */
68
+ async function onAnswer(item: FollowUpItem, resolution: FollowUpResolution = 'answered') {
60
69
  const id = execId()
61
70
  const answer = (drafts[item.id] ?? '').trim()
62
71
  if (!id || !answer) return
63
72
  // Clear the draft only once the answer is actually recorded: clearing first would make a failed
64
73
  // send cost the typed answer, and would also leave the unsaved guard below with nothing to protect.
65
74
  await followUps
66
- .answerItem(id, item.id, answer)
75
+ .answerItem(id, item.id, answer, resolution)
67
76
  .then(() => {
68
77
  delete drafts[item.id]
69
78
  })
@@ -103,6 +112,7 @@ const STATUS_LABEL_KEYS: Record<FollowUpItem['status'], string> = {
103
112
  filed: 'followUp.status.filed',
104
113
  queued: 'followUp.status.queued',
105
114
  answered: 'followUp.status.answered',
115
+ closed: 'followUp.status.closed',
106
116
  dismissed: 'followUp.status.dismissed',
107
117
  }
108
118
 
@@ -114,8 +124,14 @@ const STATUS_META: Record<
114
124
  filed: { badge: 'success', text: 'text-emerald-300' },
115
125
  queued: { badge: 'info', text: 'text-sky-300' },
116
126
  answered: { badge: 'info', text: 'text-sky-300' },
127
+ closed: { badge: 'neutral', text: 'text-slate-300' },
117
128
  dismissed: { badge: 'neutral', text: 'text-slate-400' },
118
129
  }
130
+
131
+ /** Whether a decided item carries a recorded reply to show ('answered' or 'closed'). */
132
+ function hasRecordedAnswer(item: FollowUpItem): boolean {
133
+ return (item.status === 'answered' || item.status === 'closed') && !!item.answer
134
+ }
119
135
  </script>
120
136
 
121
137
  <template>
@@ -197,13 +213,19 @@ const STATUS_META: Record<
197
213
  {{ item.ticketExternalId ?? t('followUp.viewIssue') }}
198
214
  </a>
199
215
  </p>
200
- <p
201
- v-if="item.status === 'answered' && item.answer"
202
- class="mt-1 text-[11px] text-slate-300"
203
- >
204
- <span class="text-slate-500">{{ t('followUp.yourAnswer') }}</span>
216
+ <p v-if="hasRecordedAnswer(item)" class="mt-1 text-[11px] text-slate-300">
217
+ <span class="text-slate-500">
218
+ {{
219
+ item.status === 'closed' ? t('followUp.yourRuling') : t('followUp.yourAnswer')
220
+ }}
221
+ </span>
205
222
  {{ item.answer }}
206
223
  </p>
224
+ <!-- A decision that was made and then thrown away when the budget ran out. It must
225
+ not read like one the Coder acted on. -->
226
+ <p v-if="item.sendBackDropped" class="mt-1 text-[11px] text-amber-300">
227
+ {{ t('followUp.sendBackDropped') }}
228
+ </p>
207
229
 
208
230
  <!-- Actions (only while the item is still undecided) -->
209
231
  <div v-if="item.status === 'pending'" class="mt-2.5">
@@ -215,7 +237,10 @@ const STATUS_META: Record<
215
237
  :placeholder="t('followUp.answerPlaceholder')"
216
238
  class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/60"
217
239
  />
218
- <div class="flex items-center gap-2">
240
+ <!-- Wraps, like the follow-up row below: three buttons whose labels are two
241
+ words each in English are one long line in most of the other locales, and
242
+ the result window is a narrow panel. -->
243
+ <div class="flex flex-wrap items-center gap-2">
219
244
  <UButton
220
245
  size="xs"
221
246
  color="primary"
@@ -226,6 +251,21 @@ const STATUS_META: Record<
226
251
  >
227
252
  {{ t('followUp.actions.answerAndSend') }}
228
253
  </UButton>
254
+ <UButton
255
+ size="xs"
256
+ color="neutral"
257
+ variant="subtle"
258
+ :loading="followUps.isActing(item.id)"
259
+ :disabled="!(drafts[item.id] ?? '').trim() || !access.canExecuteRuns.value"
260
+ :title="
261
+ access.canExecuteRuns.value
262
+ ? t('followUp.actions.answerAndCloseHint')
263
+ : t('access.noRunExecute')
264
+ "
265
+ @click="onAnswer(item, 'closed')"
266
+ >
267
+ {{ t('followUp.actions.answerAndClose') }}
268
+ </UButton>
229
269
  <UButton
230
270
  size="xs"
231
271
  color="neutral"
@@ -554,7 +554,7 @@ async function copyAutoSetupCommand() {
554
554
  <UInput
555
555
  v-model="form.namespaceTemplate"
556
556
  class="font-mono"
557
- placeholder="cf-env-{{pullNumber}}"
557
+ placeholder="cf-env-pr{{pullNumber}}"
558
558
  />
559
559
  </UFormField>
560
560
 
@@ -406,7 +406,7 @@ function optional(label: string): string {
406
406
  <UInput
407
407
  v-model="form.namespaceTemplate"
408
408
  class="font-mono"
409
- placeholder="cf-env-{{pullNumber}}"
409
+ placeholder="cf-env-pr{{pullNumber}}"
410
410
  />
411
411
  </UFormField>
412
412
 
@@ -5,6 +5,7 @@ import {
5
5
  getFollowUpsContract,
6
6
  queueFollowUpContract,
7
7
  } from '@cat-factory/contracts'
8
+ import type { FollowUpResolution } from '~/types/execution'
8
9
  import type { ApiContext } from './context'
9
10
 
10
11
  /**
@@ -34,12 +35,19 @@ export function followUpsApi({ send, ws }: ApiContext) {
34
35
  pathParams: { executionId, itemId },
35
36
  }),
36
37
 
37
- // Answer a question item (folded into the Coder's next pass).
38
- answerFollowUp: (workspaceId: string, executionId: string, itemId: string, answer: string) =>
38
+ // Answer a question item: 'answered' folds it into the Coder's next pass, 'closed' records a
39
+ // ruling that buys no pass (the Coder still sees it, as something already settled).
40
+ answerFollowUp: (
41
+ workspaceId: string,
42
+ executionId: string,
43
+ itemId: string,
44
+ answer: string,
45
+ resolution: FollowUpResolution = 'answered',
46
+ ) =>
39
47
  send(answerFollowUpContract, {
40
48
  pathPrefix: ws(workspaceId),
41
49
  pathParams: { executionId, itemId },
42
- body: { answer },
50
+ body: { answer, resolution },
43
51
  }),
44
52
 
45
53
  // Dismiss a follow-up / question item without acting on it.
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import type { ExecutionInstance } from '~/types/execution'
3
+ import { useExecutionStore } from '~/stores/execution'
4
+ import { useFollowUpsStore } from '~/stores/followUps'
5
+ import { useWorkspaceStore } from '~/stores/workspace'
6
+
7
+ // This store imports `useApi` by path rather than taking the Nuxt auto-import, so the setup file's
8
+ // inert `vi.stubGlobal('useApi', …)` does not reach it; the module is mocked instead.
9
+ const api = {
10
+ answerFollowUp: vi.fn(),
11
+ dismissFollowUp: vi.fn(),
12
+ fileFollowUp: vi.fn(),
13
+ queueFollowUp: vi.fn(),
14
+ }
15
+ vi.mock('~/composables/useApi', () => ({ useApi: () => api }))
16
+
17
+ // A pipeline may place MORE THAN ONE follow-up-enabled Coder, and the engine decides each item on
18
+ // the step that surfaced it (`FollowUpGateController.loadFollowUpItem` routes by item id, and says
19
+ // in as many words that it may not pick "the first enabled step"). The optimistic echo has to
20
+ // agree: the response is the OWNING step's state, so echoing it anywhere else paints one Coder's
21
+ // items, statuses and dropped-send-back stamps over another's until the stream repairs it.
22
+
23
+ const item = (id: string, over: Record<string, unknown> = {}) => ({
24
+ id,
25
+ kind: 'question',
26
+ title: `Question ${id}`,
27
+ detail: '',
28
+ status: 'pending',
29
+ createdAt: 0,
30
+ updatedAt: 0,
31
+ ...over,
32
+ })
33
+
34
+ /** Two Coder steps, each with its own surfaced item. */
35
+ const run = (rev: number): ExecutionInstance =>
36
+ ({
37
+ id: 'e1',
38
+ blockId: 'b1',
39
+ rev,
40
+ currentStep: 1,
41
+ steps: [
42
+ {
43
+ agentKind: 'coder',
44
+ followUps: { enabled: true, loops: 0, maxLoops: 3, items: [item('fu_1')] },
45
+ },
46
+ {
47
+ agentKind: 'coder',
48
+ followUps: { enabled: true, loops: 0, maxLoops: 3, items: [item('fu_2')] },
49
+ },
50
+ ],
51
+ }) as unknown as ExecutionInstance
52
+
53
+ const stepState = (index: number) => useExecutionStore().getInstance('e1')!.steps[index]!.followUps!
54
+
55
+ describe('followUps store: the optimistic echo lands on the item’s OWN step', () => {
56
+ beforeEach(() => {
57
+ vi.clearAllMocks()
58
+ useWorkspaceStore().workspaceId = 'ws1'
59
+ })
60
+
61
+ it('echoes a second Coder’s answer onto that Coder, leaving the first untouched', async () => {
62
+ // What the server returns for `fu_2`: the SECOND step's state, with the item now ruled on.
63
+ const answered = {
64
+ enabled: true,
65
+ loops: 0,
66
+ maxLoops: 3,
67
+ items: [item('fu_2', { status: 'closed', answer: 'The brief stands.' })],
68
+ }
69
+ api.answerFollowUp.mockResolvedValue(answered)
70
+ const execution = useExecutionStore()
71
+ execution.hydrate([run(1)], 'ws1')
72
+
73
+ await useFollowUpsStore().answerItem('e1', 'fu_2', 'The brief stands.', 'closed')
74
+
75
+ expect(stepState(1).items[0]!.status).toBe('closed')
76
+ // Routed by the FIRST enabled step this was `closed` too, and the first Coder's own card
77
+ // reported a decision nobody made on it.
78
+ expect(stepState(0).items.map((i) => i.id)).toEqual(['fu_1'])
79
+ expect(stepState(0).items[0]!.status).toBe('pending')
80
+ })
81
+
82
+ it('leaves both steps alone when the response names no step it can find', async () => {
83
+ // A run whose cached copy predates the item (the stream has not delivered it yet) must not
84
+ // fall back to painting the response somewhere: doing nothing is what the stream repairs.
85
+ api.dismissFollowUp.mockResolvedValue({ enabled: true, loops: 0, maxLoops: 3, items: [] })
86
+ const execution = useExecutionStore()
87
+ execution.hydrate([run(1)], 'ws1')
88
+
89
+ await useFollowUpsStore().dismissItem('e1', 'fu_unknown')
90
+
91
+ expect(stepState(0).items[0]!.status).toBe('pending')
92
+ expect(stepState(1).items[0]!.status).toBe('pending')
93
+ })
94
+ })
@@ -3,6 +3,7 @@ import { ref } from 'vue'
3
3
  import { useApi } from '~/composables/useApi'
4
4
  import { useWorkspaceStore } from '~/stores/workspace'
5
5
  import { useExecutionStore } from '~/stores/execution'
6
+ import type { FollowUpResolution } from '~/types/execution'
6
7
 
7
8
  /**
8
9
  * The Follow-up companion action surface. The live item state lives on the run's Coder step
@@ -49,7 +50,13 @@ export const useFollowUpsStore = defineStore('followUps', () => {
49
50
  executionId,
50
51
  () => call(workspace.requireId()),
51
52
  (state, instance) => {
52
- const step = instance.steps.find((s) => s.followUps?.enabled)
53
+ // Routed by ITEM ID, matching what the engine does with the same request
54
+ // (`FollowUpGateController.loadFollowUpItem`): a pipeline may carry more than one
55
+ // follow-up-enabled Coder step, and the response is that step's state. Echoing it onto
56
+ // "the first enabled step" would paint a later Coder's items, statuses and dropped-
57
+ // send-back stamps over an earlier Coder's own, so the window shows the wrong step's
58
+ // list until the stream repairs it and the earlier card reports decisions it never had.
59
+ const step = instance.steps.find((s) => s.followUps?.items.some((i) => i.id === itemId))
53
60
  if (step && state && typeof state === 'object') {
54
61
  step.followUps = state as typeof step.followUps
55
62
  }
@@ -69,8 +76,15 @@ export const useFollowUpsStore = defineStore('followUps', () => {
69
76
  const queueItem = (executionId: string, itemId: string) =>
70
77
  act(executionId, itemId, (ws) => api.queueFollowUp(ws, executionId, itemId))
71
78
 
72
- const answerItem = (executionId: string, itemId: string, answer: string) =>
73
- act(executionId, itemId, (ws) => api.answerFollowUp(ws, executionId, itemId, answer))
79
+ const answerItem = (
80
+ executionId: string,
81
+ itemId: string,
82
+ answer: string,
83
+ resolution: FollowUpResolution = 'answered',
84
+ ) =>
85
+ act(executionId, itemId, (ws) =>
86
+ api.answerFollowUp(ws, executionId, itemId, answer, resolution),
87
+ )
74
88
 
75
89
  const dismissItem = (executionId: string, itemId: string) =>
76
90
  act(executionId, itemId, (ws) => api.dismissFollowUp(ws, executionId, itemId))
@@ -16,7 +16,7 @@ function openWith(search: string): void {
16
16
 
17
17
  const K3S_LINK =
18
18
  '?infraSetup=local-k3s&label=Local+k3s&apiServerUrl=https%3A%2F%2F127.0.0.1%3A6443' +
19
- '&namespaceTemplate=cf-env-%7B%7BpullNumber%7D%7D&hostTemplate=%7B%7Bbranch%7D%7D.127.0.0.1.nip.io' +
19
+ '&namespaceTemplate=cf-env-pr%7B%7BpullNumber%7D%7D&hostTemplate=%7B%7Bnamespace%7D%7D.127.0.0.1.nip.io' +
20
20
  '&scheme=http&insecureSkipTlsVerify=1'
21
21
 
22
22
  /** The link a cluster published on a NON-default host port produces. */
@@ -25,7 +25,7 @@ const CUSTOM_PORT_LINK = `${K3S_LINK}&ingressPort=18080`
25
25
  /** The link the CLI emits when it could NOT establish that the cluster serves ingress URLs. */
26
26
  const NO_INGRESS_LINK =
27
27
  '?infraSetup=local-k3s&label=Local+k3s&apiServerUrl=https%3A%2F%2F127.0.0.1%3A6443' +
28
- '&namespaceTemplate=cf-env-%7B%7BpullNumber%7D%7D&insecureSkipTlsVerify=1'
28
+ '&namespaceTemplate=cf-env-pr%7B%7BpullNumber%7D%7D&insecureSkipTlsVerify=1'
29
29
 
30
30
  describe('consumeK3sSetupDeepLink', () => {
31
31
  beforeEach(() => {
@@ -45,8 +45,8 @@ describe('consumeK3sSetupDeepLink', () => {
45
45
  expect(ui.k3sSetupPrefill.value).toEqual({
46
46
  label: 'Local k3s',
47
47
  apiServerUrl: 'https://127.0.0.1:6443',
48
- namespaceTemplate: 'cf-env-{{pullNumber}}',
49
- hostTemplate: '{{branch}}.127.0.0.1.nip.io',
48
+ namespaceTemplate: 'cf-env-pr{{pullNumber}}',
49
+ hostTemplate: '{{namespace}}.127.0.0.1.nip.io',
50
50
  ingressPort: '',
51
51
  urlScheme: 'http',
52
52
  insecureSkipTlsVerify: true,
@@ -61,7 +61,7 @@ describe('consumeK3sSetupDeepLink', () => {
61
61
  ui.consumeK3sSetupDeepLink()
62
62
 
63
63
  expect(ui.k3sSetupPrefill.value?.ingressPort).toBe('18080')
64
- expect(ui.k3sSetupPrefill.value?.hostTemplate).toBe('{{branch}}.127.0.0.1.nip.io')
64
+ expect(ui.k3sSetupPrefill.value?.hostTemplate).toBe('{{namespace}}.127.0.0.1.nip.io')
65
65
  })
66
66
 
67
67
  it('strips the ingress-port param too, so a reload does not re-seed it', () => {
@@ -58,6 +58,7 @@ export type {
58
58
  PipelineStep,
59
59
  FollowUpItemKind,
60
60
  FollowUpItemStatus,
61
+ FollowUpResolution,
61
62
  FollowUpItem,
62
63
  FollowUpsStepState,
63
64
  ForkOption,
@@ -6478,16 +6478,21 @@
6478
6478
  "suggested": "Vorgeschlagen:",
6479
6479
  "viewIssue": "Issue ansehen",
6480
6480
  "yourAnswer": "Deine Antwort:",
6481
- "answerPlaceholder": "Beantworte diese Frage – sie fließt in den nächsten Durchlauf des Coders ein…",
6481
+ "yourRuling": "Deine Entscheidung:",
6482
+ "sendBackDropped": "Nie an den Coder gesendet: das Budget für Rückläufe war bereits aufgebraucht.",
6483
+ "answerPlaceholder": "Beantworte diese Frage und sende sie an den Coder zurück oder schließe sie als erledigt ab…",
6482
6484
  "status": {
6483
6485
  "pending": "Braucht eine Entscheidung",
6484
6486
  "filed": "Als Issue eingereicht",
6485
6487
  "queued": "An Coder gesendet",
6486
6488
  "answered": "Beantwortet",
6489
+ "closed": "Entschieden",
6487
6490
  "dismissed": "Verworfen"
6488
6491
  },
6489
6492
  "actions": {
6490
6493
  "answerAndSend": "Antworten & zurücksenden",
6494
+ "answerAndClose": "Antworten & abschließen",
6495
+ "answerAndCloseHint": "Als erledigt festhalten, ohne einen weiteren Coder-Durchlauf zu verbrauchen.",
6491
6496
  "dismiss": "Verwerfen",
6492
6497
  "fileAsIssue": "Als Issue einreichen",
6493
6498
  "sendToCoder": "An Coder senden"
@@ -6199,16 +6199,21 @@
6199
6199
  "suggested": "Suggested:",
6200
6200
  "viewIssue": "View issue",
6201
6201
  "yourAnswer": "Your answer:",
6202
- "answerPlaceholder": "Answer this question — it's folded into the Coder's next pass…",
6202
+ "yourRuling": "Your ruling:",
6203
+ "sendBackDropped": "Never sent to the Coder: the send-back budget was already spent.",
6204
+ "answerPlaceholder": "Answer this question, then send it back to the Coder or close it as settled…",
6203
6205
  "status": {
6204
6206
  "pending": "Needs a decision",
6205
6207
  "filed": "Filed as issue",
6206
6208
  "queued": "Sent to Coder",
6207
6209
  "answered": "Answered",
6210
+ "closed": "Ruled on",
6208
6211
  "dismissed": "Dismissed"
6209
6212
  },
6210
6213
  "actions": {
6211
6214
  "answerAndSend": "Answer & send back",
6215
+ "answerAndClose": "Answer & close",
6216
+ "answerAndCloseHint": "Record this as settled without spending another Coder pass.",
6212
6217
  "dismiss": "Dismiss",
6213
6218
  "fileAsIssue": "File as issue",
6214
6219
  "sendToCoder": "Send to Coder"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "Sugerido:",
5902
5902
  "viewIssue": "Ver incidencia",
5903
5903
  "yourAnswer": "Tu respuesta:",
5904
- "answerPlaceholder": "Responde a esta pregunta; se integrará en la próxima pasada del Coder…",
5904
+ "yourRuling": "Tu decisión:",
5905
+ "sendBackDropped": "Nunca se envió al Coder: el presupuesto de reenvíos ya estaba agotado.",
5906
+ "answerPlaceholder": "Responde a esta pregunta y devuélvela al Coder o ciérrala como zanjada…",
5905
5907
  "status": {
5906
5908
  "pending": "Requiere una decisión",
5907
5909
  "filed": "Registrado como incidencia",
5908
5910
  "queued": "Enviado al Coder",
5909
5911
  "answered": "Respondido",
5912
+ "closed": "Resuelto",
5910
5913
  "dismissed": "Descartado"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "Responder y devolver",
5917
+ "answerAndClose": "Responder y cerrar",
5918
+ "answerAndCloseHint": "Registrarlo como zanjado sin gastar otra pasada del Coder.",
5914
5919
  "dismiss": "Descartar",
5915
5920
  "fileAsIssue": "Registrar como incidencia",
5916
5921
  "sendToCoder": "Enviar al Coder"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "Suggestion :",
5902
5902
  "viewIssue": "Voir le ticket",
5903
5903
  "yourAnswer": "Votre réponse :",
5904
- "answerPlaceholder": "Répondez à cette question ; elle sera intégrée à la prochaine passe du Coder…",
5904
+ "yourRuling": "Votre décision :",
5905
+ "sendBackDropped": "Jamais transmis au Coder : le budget de renvois était déjà épuisé.",
5906
+ "answerPlaceholder": "Répondez à cette question, puis renvoyez-la au Coder ou clôturez-la comme réglée…",
5905
5907
  "status": {
5906
5908
  "pending": "Nécessite une décision",
5907
5909
  "filed": "Enregistré comme ticket",
5908
5910
  "queued": "Envoyé au Coder",
5909
5911
  "answered": "Répondu",
5912
+ "closed": "Tranché",
5910
5913
  "dismissed": "Rejeté"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "Répondre et renvoyer",
5917
+ "answerAndClose": "Répondre et clore",
5918
+ "answerAndCloseHint": "Enregistrer comme réglé sans dépenser une passe supplémentaire du Coder.",
5914
5919
  "dismiss": "Rejeter",
5915
5920
  "fileAsIssue": "Enregistrer comme ticket",
5916
5921
  "sendToCoder": "Envoyer au Coder"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "מוצע:",
5902
5902
  "viewIssue": "הצג ניושן",
5903
5903
  "yourAnswer": "התשובה שלך:",
5904
- "answerPlaceholder": "ענה על שאלה זו — היא משולבת במעבר הבא של ה-Coder…",
5904
+ "yourRuling": "ההכרעה שלך:",
5905
+ "sendBackDropped": "מעולם לא נשלח ל-Coder: תקציב ההחזרות כבר אזל.",
5906
+ "answerPlaceholder": "ענה על שאלה זו, ולאחר מכן שלח אותה בחזרה ל-Coder או סגור אותה כמוסדרת…",
5905
5907
  "status": {
5906
5908
  "pending": "דורש החלטה",
5907
5909
  "filed": "הוגש כניושן",
5908
5910
  "queued": "נשלח ל-Coder",
5909
5911
  "answered": "נענה",
5912
+ "closed": "הוכרע",
5910
5913
  "dismissed": "נדחה"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "ענה ושלח בחזרה",
5917
+ "answerAndClose": "לענות ולסגור",
5918
+ "answerAndCloseHint": "לתעד כמוסדר בלי לבזבז מעבר נוסף של ה-Coder.",
5914
5919
  "dismiss": "דחה",
5915
5920
  "fileAsIssue": "הגש כניושן",
5916
5921
  "sendToCoder": "שלח ל-Coder"
@@ -6478,16 +6478,21 @@
6478
6478
  "suggested": "Suggerito:",
6479
6479
  "viewIssue": "Visualizza issue",
6480
6480
  "yourAnswer": "La tua risposta:",
6481
- "answerPlaceholder": "Rispondi a questa domanda — verrà integrata nel prossimo passaggio del Coder…",
6481
+ "yourRuling": "La tua decisione:",
6482
+ "sendBackDropped": "Mai inviato al Coder: il budget dei rinvii era già esaurito.",
6483
+ "answerPlaceholder": "Rispondi a questa domanda, poi inviala al Coder o chiudila come risolta…",
6482
6484
  "status": {
6483
6485
  "pending": "Richiede una decisione",
6484
6486
  "filed": "Registrato come issue",
6485
6487
  "queued": "Inviato al Coder",
6486
6488
  "answered": "Risposto",
6489
+ "closed": "Deciso",
6487
6490
  "dismissed": "Ignorato"
6488
6491
  },
6489
6492
  "actions": {
6490
6493
  "answerAndSend": "Rispondi e invia",
6494
+ "answerAndClose": "Rispondi e chiudi",
6495
+ "answerAndCloseHint": "Registralo come risolto senza spendere un altro passaggio del Coder.",
6491
6496
  "dismiss": "Ignora",
6492
6497
  "fileAsIssue": "Registra come issue",
6493
6498
  "sendToCoder": "Invia al Coder"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "提案:",
5902
5902
  "viewIssue": "issueを表示",
5903
5903
  "yourAnswer": "あなたの回答:",
5904
- "answerPlaceholder": "この質問に回答してください。Coderの次のパスに反映されます…",
5904
+ "yourRuling": "あなたの裁定:",
5905
+ "sendBackDropped": "Coderには送信されませんでした: 差し戻しの予算をすでに使い切っていました。",
5906
+ "answerPlaceholder": "この質問に回答し、Coderに返送するか、決着済みとして確定してください…",
5905
5907
  "status": {
5906
5908
  "pending": "判断が必要",
5907
5909
  "filed": "issueとして登録済み",
5908
5910
  "queued": "Coderに送信済み",
5909
5911
  "answered": "回答済み",
5912
+ "closed": "裁定済み",
5910
5913
  "dismissed": "却下済み"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "回答して返送",
5917
+ "answerAndClose": "回答して確定",
5918
+ "answerAndCloseHint": "Coderの再実行を消費せずに決着済みとして記録します。",
5914
5919
  "dismiss": "却下",
5915
5920
  "fileAsIssue": "issueとして登録",
5916
5921
  "sendToCoder": "Coderに送信"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "Sugerowane:",
5902
5902
  "viewIssue": "Zobacz zgłoszenie",
5903
5903
  "yourAnswer": "Twoja odpowiedź:",
5904
- "answerPlaceholder": "Odpowiedz na to pytanie — zostanie uwzględnione w kolejnym przebiegu Codera…",
5904
+ "yourRuling": "Twoje rozstrzygnięcie:",
5905
+ "sendBackDropped": "Nigdy nie trafiło do Codera: budżet zawrotek był już wyczerpany.",
5906
+ "answerPlaceholder": "Odpowiedz na to pytanie, a następnie odeślij je do Codera albo zamknij jako rozstrzygnięte…",
5905
5907
  "status": {
5906
5908
  "pending": "Wymaga decyzji",
5907
5909
  "filed": "Zgłoszone jako zgłoszenie",
5908
5910
  "queued": "Wysłane do Codera",
5909
5911
  "answered": "Odpowiedziano",
5912
+ "closed": "Rozstrzygnięte",
5910
5913
  "dismissed": "Odrzucone"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "Odpowiedz i odeślij",
5917
+ "answerAndClose": "Odpowiedz i zamknij",
5918
+ "answerAndCloseHint": "Zapisz jako rozstrzygnięte bez zużywania kolejnego przebiegu Codera.",
5914
5919
  "dismiss": "Odrzuć",
5915
5920
  "fileAsIssue": "Zgłoś jako zgłoszenie",
5916
5921
  "sendToCoder": "Wyślij do Codera"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "Önerilen:",
5902
5902
  "viewIssue": "Sorunu görüntüle",
5903
5903
  "yourAnswer": "Cevabınız:",
5904
- "answerPlaceholder": "Bu soruyu cevaplayın — Coder'ın bir sonraki turuna dahil edilir…",
5904
+ "yourRuling": "Kararınız:",
5905
+ "sendBackDropped": "Coder'a hiç gönderilmedi: geri gönderim bütçesi çoktan tükenmişti.",
5906
+ "answerPlaceholder": "Bu soruyu cevaplayın, ardından Coder'a geri gönderin veya çözülmüş olarak kapatın…",
5905
5907
  "status": {
5906
5908
  "pending": "Karar gerekiyor",
5907
5909
  "filed": "Sorun olarak kaydedildi",
5908
5910
  "queued": "Coder'a gönderildi",
5909
5911
  "answered": "Cevaplandı",
5912
+ "closed": "Karara bağlandı",
5910
5913
  "dismissed": "Reddedildi"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "Cevapla ve geri gönder",
5917
+ "answerAndClose": "Yanıtla ve kapat",
5918
+ "answerAndCloseHint": "Yeni bir Coder turu harcamadan çözülmüş olarak kaydet.",
5914
5919
  "dismiss": "Reddet",
5915
5920
  "fileAsIssue": "Sorun olarak kaydet",
5916
5921
  "sendToCoder": "Coder'a gönder"
@@ -5901,16 +5901,21 @@
5901
5901
  "suggested": "Пропозиція:",
5902
5902
  "viewIssue": "Переглянути тікет",
5903
5903
  "yourAnswer": "Ваша відповідь:",
5904
- "answerPlaceholder": "Дайте відповідь на це запитання — її буде враховано в наступному проході Codera…",
5904
+ "yourRuling": "Ваше рішення:",
5905
+ "sendBackDropped": "Ніколи не надіслано до Coder: бюджет повернень уже вичерпано.",
5906
+ "answerPlaceholder": "Дайте відповідь на це запитання, а потім поверніть його до Coder або закрийте як вирішене…",
5905
5907
  "status": {
5906
5908
  "pending": "Потребує рішення",
5907
5909
  "filed": "Зареєстровано як тікет",
5908
5910
  "queued": "Надіслано Coder",
5909
5911
  "answered": "Відповіли",
5912
+ "closed": "Вирішено",
5910
5913
  "dismissed": "Відхилено"
5911
5914
  },
5912
5915
  "actions": {
5913
5916
  "answerAndSend": "Відповісти й повернути",
5917
+ "answerAndClose": "Відповісти й закрити",
5918
+ "answerAndCloseHint": "Записати як вирішене, не витрачаючи ще один прохід Coder.",
5914
5919
  "dismiss": "Відхилити",
5915
5920
  "fileAsIssue": "Зареєструвати як тікет",
5916
5921
  "sendToCoder": "Надіслати Coder"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.284.0",
3
+ "version": "0.285.1",
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",
@@ -18,13 +18,14 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
+ "@cat-factory/contracts": "0.328.0",
21
22
  "@modular-frontend/core": "0.6.0",
22
23
  "@modular-vue/core": "^1.5.0",
23
24
  "@modular-vue/journeys": "^1.4.0",
24
25
  "@modular-vue/nuxt": "^0.4.1",
25
26
  "@modular-vue/runtime": "^1.4.1",
26
27
  "@modular-vue/vue": "^1.4.1",
27
- "@nuxt/ui": "^4.10.0",
28
+ "@nuxt/ui": "^4.11.0",
28
29
  "@nuxtjs/i18n": "^10.6.0",
29
30
  "@pinia/nuxt": "^1.0.2",
30
31
  "@toad-contracts/core": "0.4.0",
@@ -39,18 +40,17 @@
39
40
  "pinia-plugin-persistedstate": "^4.7.1",
40
41
  "valibot": "^1.4.2",
41
42
  "vue": "3.5.41",
42
- "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.326.0"
43
+ "wretch": "^3.0.9"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",
47
- "happy-dom": "^20.11.2",
47
+ "happy-dom": "^20.11.6",
48
48
  "msw": "^2.15.0",
49
49
  "nuxt": "^4.5.2",
50
50
  "typescript": "^6.0.3",
51
51
  "vitest": "^4.1.11",
52
52
  "vue-i18n-extract": "^2.0.7",
53
- "vue-tsc": "^3.3.10"
53
+ "vue-tsc": "^3.3.11"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "nuxt": "^4.5.2"