@cat-factory/app 0.118.0 → 0.119.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.
@@ -3,14 +3,21 @@
3
3
  // prioritized findings, opened via the universal result-view host. It reads the live review
4
4
  // state straight off the run's `pr-reviewer` step (`step.prReview`, kept fresh by the
5
5
  // execution stream) and lets a human multi-SELECT which findings matter, grouped by slice and
6
- // sorted by severity, then finish the review. The Fixer / inline-comment resolutions are the
7
- // tracked PR 3 follow-up; this window's `Finish review` records the curated selection.
6
+ // sorted by severity, then resolve the review one of three ways: `Fix` (feed the selected
7
+ // findings to a Fixer that commits fixes onto the PR branch), `Post` (publish them as inline PR
8
+ // review comments), or `Finish` (just record the curated selection). Fix/Post act on the
9
+ // selection, so they require at least one selected finding.
8
10
  import { computed, ref, watch } from 'vue'
9
11
  import { useResultView } from '~/composables/useResultView'
10
12
  import { useExecutionStore } from '~/stores/execution'
11
13
  import { useBoardStore } from '~/stores/board'
12
14
  import { usePrReviewStore } from '~/stores/prReview'
13
- import type { PrReviewFinding, PrReviewSeverity, PrReviewStepState } from '~/types/execution'
15
+ import type {
16
+ PrReviewFinding,
17
+ PrReviewResolution,
18
+ PrReviewSeverity,
19
+ PrReviewStepState,
20
+ } from '~/types/execution'
14
21
 
15
22
  const execution = useExecutionStore()
16
23
  const board = useBoardStore()
@@ -35,6 +42,9 @@ const step = computed(() => {
35
42
  const state = computed<PrReviewStepState | null>(() => step.value?.prReview ?? null)
36
43
  const status = computed(() => state.value?.status ?? null)
37
44
  const awaiting = computed(() => status.value === 'awaiting_selection')
45
+ // A resolution is executing (the Fixer is committing, or comments are being posted) — show a
46
+ // working state between the human's choice and the run advancing/the stream echoing `done`.
47
+ const working = computed(() => status.value === 'fixing' || status.value === 'posting')
38
48
  const findings = computed<PrReviewFinding[]>(() => state.value?.findings ?? [])
39
49
 
40
50
  /** Severity → chip classes (styling, not copy). */
@@ -97,12 +107,16 @@ function clearAll(): void {
97
107
  selected.value = new Set()
98
108
  }
99
109
 
100
- const canFinish = computed(() => awaiting.value && !prReview.resolving)
110
+ const canResolve = computed(() => awaiting.value && !prReview.resolving)
111
+ // Fix / Post act on the selection, so they need at least one selected finding; Finish always
112
+ // works (it just records the — possibly empty — curated selection and completes the review).
113
+ const hasSelection = computed(() => selected.value.size > 0)
101
114
 
102
- async function onFinish(): Promise<void> {
115
+ async function onResolve(action: PrReviewResolution): Promise<void> {
103
116
  const id = instanceId.value
104
- if (!id || !canFinish.value) return
105
- await prReview.resolve(id, [...selected.value]).catch(() => {})
117
+ if (!id || !canResolve.value) return
118
+ if ((action === 'fix' || action === 'post') && !hasSelection.value) return
119
+ await prReview.resolve(id, [...selected.value], action).catch(() => {})
106
120
  }
107
121
  </script>
108
122
 
@@ -162,6 +176,21 @@ async function onFinish(): Promise<void> {
162
176
  <p class="max-w-sm text-[11px] text-slate-500">{{ t('prReview.reviewing.hint') }}</p>
163
177
  </div>
164
178
 
179
+ <!-- A resolution is executing: the Fixer is committing / comments are being posted. -->
180
+ <div
181
+ v-else-if="working"
182
+ data-testid="pr-review-working"
183
+ class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
184
+ >
185
+ <UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
186
+ <p class="text-sm">
187
+ {{ status === 'fixing' ? t('prReview.fixing.title') : t('prReview.posting.title') }}
188
+ </p>
189
+ <p class="max-w-sm text-[11px] text-slate-500">
190
+ {{ status === 'fixing' ? t('prReview.fixing.hint') : t('prReview.posting.hint') }}
191
+ </p>
192
+ </div>
193
+
165
194
  <template v-else>
166
195
  <p
167
196
  v-if="prReview.error"
@@ -274,14 +303,32 @@ async function onFinish(): Promise<void> {
274
303
  class="flex items-center justify-end gap-2 border-t border-slate-800 px-5 py-3"
275
304
  >
276
305
  <UButton
277
- color="primary"
278
- :loading="prReview.resolving"
279
- :disabled="!canFinish"
306
+ color="neutral"
307
+ variant="ghost"
308
+ :disabled="!canResolve"
280
309
  data-testid="pr-review-finish"
281
- @click="onFinish"
310
+ @click="onResolve('finish')"
282
311
  >
283
312
  {{ t('prReview.finish') }}
284
313
  </UButton>
314
+ <UButton
315
+ color="neutral"
316
+ variant="soft"
317
+ :disabled="!canResolve || !hasSelection"
318
+ data-testid="pr-review-post"
319
+ @click="onResolve('post')"
320
+ >
321
+ {{ t('prReview.post') }}
322
+ </UButton>
323
+ <UButton
324
+ color="primary"
325
+ :loading="prReview.resolving"
326
+ :disabled="!canResolve || !hasSelection"
327
+ data-testid="pr-review-fix"
328
+ @click="onResolve('fix')"
329
+ >
330
+ {{ t('prReview.fix') }}
331
+ </UButton>
285
332
  </footer>
286
333
  </div>
287
334
  </div>
@@ -14,11 +14,12 @@ export function prReviewApi({ send, ws }: ApiContext) {
14
14
  getPrReview: (workspaceId: string, executionId: string) =>
15
15
  send(getPrReviewContract, { pathPrefix: ws(workspaceId), pathParams: { executionId } }),
16
16
 
17
- // Resolve a parked PR review: the curated finding selection + how it was resolved.
17
+ // Resolve a parked PR review: the curated finding selection + how it was resolved
18
+ // (`finish` completes it, `fix` feeds a Fixer, `post` publishes inline PR comments).
18
19
  resolvePrReview: (
19
20
  workspaceId: string,
20
21
  executionId: string,
21
- body: { action?: 'finish'; findingIds?: string[] },
22
+ body: { action?: 'finish' | 'fix' | 'post'; findingIds?: string[] },
22
23
  ) =>
23
24
  send(resolvePrReviewContract, {
24
25
  pathPrefix: ws(workspaceId),
@@ -42,6 +42,16 @@ export const useBoardStore = defineStore('board', () => {
42
42
  params ?? {},
43
43
  )
44
44
  const blocks = ref<Block[]>([])
45
+ // Client-side monotonic guard against a stale full-snapshot `hydrate` CLOBBERING newer live
46
+ // state. A run's status transitions (…→ in_progress → pr_ready/done) reach the board as
47
+ // targeted `execution`-event `upsert`s; a `refresh()` whose snapshot was FETCHED earlier (its
48
+ // block still `in_progress`) can resolve AFTER such an upsert and, since `hydrate` REPLACES the
49
+ // list, overwrite the just-applied terminal status back to the stale value — with no further
50
+ // event to restore it (the documented real-time coherence hazard; reliably hit under CI
51
+ // latency). Blocks carry no server revision, so we stamp each live `upsert` with a monotonic
52
+ // sequence and let `hydrate` preserve any block upserted AFTER the refresh's captured baseline.
53
+ let liveUpsertSeq = 0
54
+ const liveUpsertAt = new Map<string, number>()
45
55
  // Archived service frames (`archived === true`): hidden from the board but preserved and
46
56
  // restorable with no expiry. Hydrated from the snapshot's `archivedServices`; the frames
47
57
  // themselves are NOT in `blocks` (the snapshot filters an archived frame + its subtree out).
@@ -111,10 +121,22 @@ export const useBoardStore = defineStore('board', () => {
111
121
  }
112
122
  return s
113
123
  }
114
- function hydrate(next: Block[]) {
124
+ /**
125
+ * Baseline for {@link hydrate}: capture this BEFORE a refresh's snapshot fetch and pass it
126
+ * back in, so a block that received a live `upsert` while the fetch was in flight is preserved
127
+ * (its live state is newer than the snapshot). Callers that don't pass a baseline get a plain
128
+ * full replace (initial load / board switch — no live-upsert race to guard).
129
+ */
130
+ function hydrateBaseline(): number {
131
+ return liveUpsertSeq
132
+ }
133
+ function hydrate(next: Block[], since = liveUpsertSeq) {
115
134
  const prev = new Map(blocks.value.map((b) => [b.id, b]))
116
135
  const reconciled = next.map((n) => {
117
136
  const existing = prev.get(n.id)
137
+ // A block live-`upsert`ed AFTER this refresh's fetch started is newer than the snapshot —
138
+ // keep the live version instead of clobbering it back to the stale snapshot value.
139
+ if (existing && (liveUpsertAt.get(n.id) ?? 0) > since) return existing
118
140
  return existing && jsonFor(existing) === jsonFor(n) ? existing : n
119
141
  })
120
142
  // Keep blocks the user just deleted hidden while their delete is still pending.
@@ -147,6 +169,8 @@ export const useBoardStore = defineStore('board', () => {
147
169
  function upsert(block: Block) {
148
170
  // A live event for a block awaiting its deferred delete must not resurrect it.
149
171
  if (pendingDoomed.has(block.id)) return
172
+ // Stamp the live-upsert order so a later, staler refresh `hydrate` can't clobber this.
173
+ liveUpsertAt.set(block.id, ++liveUpsertSeq)
150
174
  const i = blocks.value.findIndex((b) => b.id === block.id)
151
175
  if (i >= 0) blocks.value[i] = block
152
176
  else blocks.value.push(block)
@@ -612,6 +636,7 @@ export const useBoardStore = defineStore('board', () => {
612
636
  blocks,
613
637
  archived,
614
638
  hydrate,
639
+ hydrateBaseline,
615
640
  hydrateArchived,
616
641
  upsert,
617
642
  ...queries,
@@ -1,6 +1,6 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref } from 'vue'
3
- import type { PrReviewStepState } from '~/types/execution'
3
+ import type { PrReviewResolution, PrReviewStepState } from '~/types/execution'
4
4
  import { useApi } from '~/composables/useApi'
5
5
  import { useWorkspaceStore } from '~/stores/workspace'
6
6
  import { useExecutionStore } from '~/stores/execution'
@@ -56,15 +56,21 @@ export const usePrReviewStore = defineStore('prReview', () => {
56
56
  }
57
57
 
58
58
  /**
59
- * Resolve the review: record the curated finding selection and complete the read-only review
60
- * (the run then advances to done). PR 2 supports only the `finish` action.
59
+ * Resolve the review: record the curated finding selection and act on it. `finish` completes
60
+ * the read-only review; `fix` feeds the selected findings to a Fixer (which commits fixes onto
61
+ * the reviewed PR's branch); `post` publishes them as inline PR review comments. The run then
62
+ * advances (or re-dispatches for `fix`). `fix`/`post` require ≥1 selected finding.
61
63
  */
62
- async function resolve(executionId: string, findingIds: string[]): Promise<void> {
64
+ async function resolve(
65
+ executionId: string,
66
+ findingIds: string[],
67
+ action: PrReviewResolution = 'finish',
68
+ ): Promise<void> {
63
69
  error.value = null
64
70
  resolving.value = true
65
71
  try {
66
72
  const state = await api.resolvePrReview(workspace.requireId(), executionId, {
67
- action: 'finish',
73
+ action,
68
74
  findingIds,
69
75
  })
70
76
  reflect(executionId, state as PrReviewStepState)
@@ -111,6 +111,44 @@ describe('workspace store refresh ordering', () => {
111
111
  // The fresh snapshot won and the stale one was discarded: the spawned card survives.
112
112
  expect(board.getBlock('spawned')).toBeDefined()
113
113
  })
114
+
115
+ // Regression for the SECOND clobber axis: a refresh vs an interleaved live `upsert`. The
116
+ // `refreshSeq` guard above only orders refreshes against each OTHER — it does nothing when a
117
+ // single refresh's (slow) fetch overlaps a targeted live event. A run's status transitions
118
+ // (…→ in_progress → pr_ready/done) arrive as `execution`-event `board.upsert`s; a refresh whose
119
+ // snapshot was FETCHED while the block was still `in_progress` must not, on resolving later,
120
+ // replace that block back to the stale status. This was the reliable-under-CI-latency e2e
121
+ // timeout where a run never showed a terminal `data-status`. The board store now stamps each
122
+ // live upsert and `refresh()` captures a baseline before its fetch so the newer live state wins.
123
+ it('a refresh started before a live upsert does not clobber the newer live status', async () => {
124
+ const frame = block('f1')
125
+ const task = block('t1', { level: 'task', parentId: 'f1', status: 'in_progress' })
126
+ let resolveRefresh!: (s: WorkspaceSnapshot) => void
127
+ const getWorkspace = vi
128
+ .fn()
129
+ // 1) switchTo — the task is mid-run (`in_progress`).
130
+ .mockResolvedValueOnce(snapshot('ws1', [frame, task]))
131
+ // 2) a refresh whose fetch is in flight while a live terminal event lands.
132
+ .mockReturnValueOnce(new Promise<WorkspaceSnapshot>((r) => (resolveRefresh = r)))
133
+ vi.stubGlobal('useApi', () => ({ getWorkspace }))
134
+
135
+ const ws = useWorkspaceStore()
136
+ const board = useBoardStore()
137
+ await ws.switchTo('ws1')
138
+ expect(board.getBlock('t1')?.status).toBe('in_progress')
139
+
140
+ // A refresh starts (captures the board baseline; its snapshot still shows `in_progress`).
141
+ const pass = ws.refresh()
142
+ // A live execution event lands mid-fetch: the run reached terminal, so the block is `done`.
143
+ board.upsert(block('t1', { level: 'task', parentId: 'f1', status: 'done' }))
144
+ expect(board.getBlock('t1')?.status).toBe('done')
145
+ // The now-stale refresh resolves with its older `in_progress` snapshot.
146
+ resolveRefresh(snapshot('ws1', [frame, block('t1', { level: 'task', parentId: 'f1' })]))
147
+ await pass
148
+
149
+ // The live terminal status survives — the stale refresh did NOT clobber it back.
150
+ expect(board.getBlock('t1')?.status).toBe('done')
151
+ })
114
152
  })
115
153
 
116
154
  // Cold-open waterfall flattening (app-startup initiative, item 8): `init()` fetches the persisted
@@ -85,8 +85,13 @@ export const useWorkspaceStore = defineStore(
85
85
  () => workspaces.value.find((w) => w.id === workspaceId.value) ?? null,
86
86
  )
87
87
 
88
- /** Push a snapshot into the data stores. */
89
- function hydrate(snapshot: WorkspaceSnapshot) {
88
+ /**
89
+ * Push a snapshot into the data stores. `boardSince` (captured BEFORE this snapshot's fetch)
90
+ * lets the board store preserve any block live-`upsert`ed while the fetch was in flight, so a
91
+ * slower refresh can't clobber a newer live status (see `useBoardStore().hydrate`). Omitted by
92
+ * fresh loads (init/switch/create), where there is no in-flight-upsert race to guard.
93
+ */
94
+ function hydrate(snapshot: WorkspaceSnapshot, boardSince?: number) {
90
95
  // A change of active board (or the first load) — drop the per-block caches that are
91
96
  // NOT part of the snapshot (reviews, brainstorm/consensus sessions, the GitHub
92
97
  // projection) so a switched-to board never shows the previous one's stale state.
@@ -116,7 +121,7 @@ export const useWorkspaceStore = defineStore(
116
121
  const i = workspaces.value.findIndex((w) => w.id === snapshot.workspace.id)
117
122
  if (i >= 0) workspaces.value[i] = snapshot.workspace
118
123
  else workspaces.value.unshift(snapshot.workspace)
119
- useBoardStore().hydrate(snapshot.blocks)
124
+ useBoardStore().hydrate(snapshot.blocks, boardSince)
120
125
  useBoardStore().hydrateArchived(snapshot.archivedServices ?? [])
121
126
  usePipelinesStore().hydrate(snapshot.pipelines, snapshot.pipelineCatalogVersions)
122
127
  useExecutionStore().hydrate(snapshot.executions, snapshot.workspace.id)
@@ -291,11 +296,17 @@ export const useWorkspaceStore = defineStore(
291
296
  const targetId = workspaceId.value
292
297
  if (!targetId) return
293
298
  const seq = ++refreshSeq
299
+ // Capture the board's live-upsert baseline BEFORE the fetch: any block upserted by a live
300
+ // event while this (potentially slow) snapshot is in flight is newer than the snapshot, so
301
+ // `hydrate` must NOT clobber it back. The `refreshSeq` guard below only orders refreshes
302
+ // against each OTHER — this guards a refresh against an interleaved live upsert (e.g. a
303
+ // run's terminal status landing mid-fetch), the coherence hazard under CI latency.
304
+ const boardSince = useBoardStore().hydrateBaseline()
294
305
  const snapshot = await api.getWorkspace(targetId)
295
306
  // A newer refresh was issued (or the active board switched) while this fetch was in flight —
296
307
  // discard this older/staler result so it can't clobber the newer hydrate.
297
308
  if (seq !== refreshSeq || workspaceId.value !== targetId) return
298
- hydrate(snapshot)
309
+ hydrate(snapshot, boardSince)
299
310
  }
300
311
 
301
312
  /** The active workspace id, or throw if the app isn't bootstrapped yet. */
@@ -43,6 +43,7 @@ export type {
43
43
  PrReviewSlice,
44
44
  PrReviewSeverity,
45
45
  PrReviewCategory,
46
+ PrReviewResolution,
46
47
  GateFailingCheck,
47
48
  GateAttempt,
48
49
  GateStepState,
@@ -4717,12 +4717,22 @@
4717
4717
  "clear": "Zurücksetzen",
4718
4718
  "selectedCount": "{count} ausgewählt",
4719
4719
  "finish": "Review abschließen",
4720
+ "fix": "Ausgewählte beheben",
4721
+ "post": "Als Kommentare posten",
4720
4722
  "suggestedFix": "Lösungsvorschlag:",
4721
4723
  "line": "Zeile {line}",
4722
4724
  "reviewing": {
4723
4725
  "title": "Pull Request wird geprüft…",
4724
4726
  "hint": "Der Diff wird in zusammenhängende Teile zerlegt und einzeln geprüft."
4725
4727
  },
4728
+ "fixing": {
4729
+ "title": "Ausgewählte Befunde werden behoben…",
4730
+ "hint": "Ein Fixer committet Änderungen für die ausgewählten Befunde auf den Pull-Request-Branch."
4731
+ },
4732
+ "posting": {
4733
+ "title": "Review-Kommentare werden gepostet…",
4734
+ "hint": "Die ausgewählten Befunde werden als Inline-Kommentare im Pull Request veröffentlicht."
4735
+ },
4726
4736
  "severity": {
4727
4737
  "blocker": "Blocker",
4728
4738
  "high": "Hoch",
@@ -4843,12 +4843,22 @@
4843
4843
  "clear": "Clear",
4844
4844
  "selectedCount": "{count} selected",
4845
4845
  "finish": "Finish review",
4846
+ "fix": "Fix selected",
4847
+ "post": "Post as comments",
4846
4848
  "suggestedFix": "Suggested fix:",
4847
4849
  "line": "line {line}",
4848
4850
  "reviewing": {
4849
4851
  "title": "Reviewing the pull request…",
4850
4852
  "hint": "Slicing the diff into cohesive chunks and reviewing each one."
4851
4853
  },
4854
+ "fixing": {
4855
+ "title": "Fixing the selected findings…",
4856
+ "hint": "A fixer is committing changes for the selected findings onto the pull request branch."
4857
+ },
4858
+ "posting": {
4859
+ "title": "Posting review comments…",
4860
+ "hint": "Publishing the selected findings as inline comments on the pull request."
4861
+ },
4852
4862
  "severity": {
4853
4863
  "blocker": "Blocker",
4854
4864
  "high": "High",
@@ -4705,12 +4705,22 @@
4705
4705
  "clear": "Limpiar",
4706
4706
  "selectedCount": "{count} seleccionados",
4707
4707
  "finish": "Finalizar revisión",
4708
+ "fix": "Corregir seleccionados",
4709
+ "post": "Publicar como comentarios",
4708
4710
  "suggestedFix": "Corrección sugerida:",
4709
4711
  "line": "línea {line}",
4710
4712
  "reviewing": {
4711
4713
  "title": "Revisando el pull request…",
4712
4714
  "hint": "Dividiendo el diff en bloques coherentes y revisando cada uno."
4713
4715
  },
4716
+ "fixing": {
4717
+ "title": "Corrigiendo los hallazgos seleccionados…",
4718
+ "hint": "Un corrector está confirmando cambios para los hallazgos seleccionados en la rama del pull request."
4719
+ },
4720
+ "posting": {
4721
+ "title": "Publicando comentarios de revisión…",
4722
+ "hint": "Publicando los hallazgos seleccionados como comentarios en línea en el pull request."
4723
+ },
4714
4724
  "severity": {
4715
4725
  "blocker": "Bloqueante",
4716
4726
  "high": "Alta",
@@ -4705,12 +4705,22 @@
4705
4705
  "clear": "Effacer",
4706
4706
  "selectedCount": "{count} sélectionné(s)",
4707
4707
  "finish": "Terminer la revue",
4708
+ "fix": "Corriger la sélection",
4709
+ "post": "Publier en commentaires",
4708
4710
  "suggestedFix": "Correction suggérée :",
4709
4711
  "line": "ligne {line}",
4710
4712
  "reviewing": {
4711
4713
  "title": "Revue de la pull request…",
4712
4714
  "hint": "Découpage du diff en blocs cohérents et revue de chacun."
4713
4715
  },
4716
+ "fixing": {
4717
+ "title": "Correction des points sélectionnés…",
4718
+ "hint": "Un correcteur valide des modifications pour les points sélectionnés sur la branche de la pull request."
4719
+ },
4720
+ "posting": {
4721
+ "title": "Publication des commentaires de revue…",
4722
+ "hint": "Publication des points sélectionnés en commentaires en ligne sur la pull request."
4723
+ },
4714
4724
  "severity": {
4715
4725
  "blocker": "Bloquant",
4716
4726
  "high": "Élevée",
@@ -4716,12 +4716,22 @@
4716
4716
  "clear": "נקה",
4717
4717
  "selectedCount": "{count} נבחרו",
4718
4718
  "finish": "סיים בדיקה",
4719
+ "fix": "תקן נבחרים",
4720
+ "post": "פרסם כהערות",
4719
4721
  "suggestedFix": "תיקון מוצע:",
4720
4722
  "line": "שורה {line}",
4721
4723
  "reviewing": {
4722
4724
  "title": "בודק את בקשת המשיכה…",
4723
4725
  "hint": "מחלק את ההבדלים לקטעים לכידים ובודק כל אחד."
4724
4726
  },
4727
+ "fixing": {
4728
+ "title": "מתקן את הממצאים שנבחרו…",
4729
+ "hint": "מתקן מבצע commit לשינויים עבור הממצאים שנבחרו אל ענף בקשת המשיכה."
4730
+ },
4731
+ "posting": {
4732
+ "title": "מפרסם הערות בדיקה…",
4733
+ "hint": "מפרסם את הממצאים שנבחרו כהערות מוטבעות בבקשת המשיכה."
4734
+ },
4725
4735
  "severity": {
4726
4736
  "blocker": "חוסם",
4727
4737
  "high": "גבוה",
@@ -4717,12 +4717,22 @@
4717
4717
  "clear": "Cancella",
4718
4718
  "selectedCount": "{count} selezionati",
4719
4719
  "finish": "Concludi revisione",
4720
+ "fix": "Correggi selezionati",
4721
+ "post": "Pubblica come commenti",
4720
4722
  "suggestedFix": "Correzione suggerita:",
4721
4723
  "line": "riga {line}",
4722
4724
  "reviewing": {
4723
4725
  "title": "Revisione della pull request…",
4724
4726
  "hint": "Suddivisione del diff in blocchi coerenti e revisione di ciascuno."
4725
4727
  },
4728
+ "fixing": {
4729
+ "title": "Correzione dei rilievi selezionati…",
4730
+ "hint": "Un fixer sta effettuando il commit delle modifiche per i rilievi selezionati sul branch della pull request."
4731
+ },
4732
+ "posting": {
4733
+ "title": "Pubblicazione dei commenti di revisione…",
4734
+ "hint": "Pubblicazione dei rilievi selezionati come commenti inline sulla pull request."
4735
+ },
4726
4736
  "severity": {
4727
4737
  "blocker": "Bloccante",
4728
4738
  "high": "Alta",
@@ -4717,12 +4717,22 @@
4717
4717
  "clear": "クリア",
4718
4718
  "selectedCount": "{count}件選択中",
4719
4719
  "finish": "レビューを完了",
4720
+ "fix": "選択項目を修正",
4721
+ "post": "コメントとして投稿",
4720
4722
  "suggestedFix": "修正案:",
4721
4723
  "line": "{line}行目",
4722
4724
  "reviewing": {
4723
4725
  "title": "プルリクエストをレビュー中…",
4724
4726
  "hint": "差分をまとまりのある単位に分割し、各単位をレビューしています。"
4725
4727
  },
4728
+ "fixing": {
4729
+ "title": "選択した指摘を修正中…",
4730
+ "hint": "フィクサーが選択した指摘の変更をプルリクエストのブランチにコミットしています。"
4731
+ },
4732
+ "posting": {
4733
+ "title": "レビューコメントを投稿中…",
4734
+ "hint": "選択した指摘をプルリクエストのインラインコメントとして公開しています。"
4735
+ },
4726
4736
  "severity": {
4727
4737
  "blocker": "ブロッカー",
4728
4738
  "high": "高",
@@ -4705,12 +4705,22 @@
4705
4705
  "clear": "Wyczyść",
4706
4706
  "selectedCount": "Wybrano: {count}",
4707
4707
  "finish": "Zakończ przegląd",
4708
+ "fix": "Napraw wybrane",
4709
+ "post": "Opublikuj jako komentarze",
4708
4710
  "suggestedFix": "Sugerowana poprawka:",
4709
4711
  "line": "wiersz {line}",
4710
4712
  "reviewing": {
4711
4713
  "title": "Przeglądanie pull requesta…",
4712
4714
  "hint": "Dzielenie zmian na spójne części i przeglądanie każdej z nich."
4713
4715
  },
4716
+ "fixing": {
4717
+ "title": "Naprawianie wybranych uwag…",
4718
+ "hint": "Fixer zatwierdza zmiany dla wybranych uwag w gałęzi pull requesta."
4719
+ },
4720
+ "posting": {
4721
+ "title": "Publikowanie komentarzy przeglądu…",
4722
+ "hint": "Publikowanie wybranych uwag jako komentarzy w treści pull requesta."
4723
+ },
4714
4724
  "severity": {
4715
4725
  "blocker": "Blokujące",
4716
4726
  "high": "Wysokie",
@@ -4717,12 +4717,22 @@
4717
4717
  "clear": "Temizle",
4718
4718
  "selectedCount": "{count} seçildi",
4719
4719
  "finish": "İncelemeyi bitir",
4720
+ "fix": "Seçilenleri düzelt",
4721
+ "post": "Yorum olarak gönder",
4720
4722
  "suggestedFix": "Önerilen düzeltme:",
4721
4723
  "line": "satır {line}",
4722
4724
  "reviewing": {
4723
4725
  "title": "Pull request inceleniyor…",
4724
4726
  "hint": "Fark tutarlı parçalara bölünüp her biri inceleniyor."
4725
4727
  },
4728
+ "fixing": {
4729
+ "title": "Seçili bulgular düzeltiliyor…",
4730
+ "hint": "Bir düzeltici, seçili bulgular için değişiklikleri pull request dalına işliyor."
4731
+ },
4732
+ "posting": {
4733
+ "title": "İnceleme yorumları gönderiliyor…",
4734
+ "hint": "Seçili bulgular pull request üzerinde satır içi yorum olarak yayımlanıyor."
4735
+ },
4726
4736
  "severity": {
4727
4737
  "blocker": "Engelleyici",
4728
4738
  "high": "Yüksek",
@@ -4705,12 +4705,22 @@
4705
4705
  "clear": "Очистити",
4706
4706
  "selectedCount": "Вибрано: {count}",
4707
4707
  "finish": "Завершити огляд",
4708
+ "fix": "Виправити вибрані",
4709
+ "post": "Опублікувати як коментарі",
4708
4710
  "suggestedFix": "Пропоноване виправлення:",
4709
4711
  "line": "рядок {line}",
4710
4712
  "reviewing": {
4711
4713
  "title": "Перевірка pull request…",
4712
4714
  "hint": "Поділ змін на цілісні частини та огляд кожної з них."
4713
4715
  },
4716
+ "fixing": {
4717
+ "title": "Виправлення вибраних зауважень…",
4718
+ "hint": "Фіксер комітить зміни для вибраних зауважень у гілку pull request."
4719
+ },
4720
+ "posting": {
4721
+ "title": "Публікація коментарів огляду…",
4722
+ "hint": "Публікація вибраних зауважень як вбудованих коментарів у pull request."
4723
+ },
4714
4724
  "severity": {
4715
4725
  "blocker": "Блокер",
4716
4726
  "high": "Високий",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.118.0",
3
+ "version": "0.119.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.131.0"
37
+ "@cat-factory/contracts": "0.132.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",