@cat-factory/app 0.272.0 → 0.273.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.
@@ -6,7 +6,18 @@
6
6
  import type { InfraEngine, ProvisionType } from '@cat-factory/contracts'
7
7
  import type { RunEnvironment, HumanTestEnvironmentStatus } from '~/types/execution'
8
8
 
9
- const props = defineProps<{ environment: RunEnvironment | null; degradedReason?: string | null }>()
9
+ const props = defineProps<{
10
+ environment: RunEnvironment | null
11
+ /**
12
+ * Whether the enclosing run is still being driven (`runIsActive`). A transitional status
13
+ * (`provisioning` / `tearing_down`) keeps its label once the run stops, because that is the
14
+ * last thing the provider reported, but its icon stops spinning: nothing is standing this
15
+ * environment up any more. Required rather than defaulted so a new call site has to say which
16
+ * run it is rendering, instead of silently inheriting a perpetual spinner.
17
+ */
18
+ runActive: boolean
19
+ degradedReason?: string | null
20
+ }>()
10
21
 
11
22
  const { t, d } = useI18n()
12
23
 
@@ -74,6 +85,13 @@ const ENV_STATUS_META = computed<
74
85
  icon: 'i-lucide-circle-off',
75
86
  },
76
87
  }))
88
+
89
+ // The two statuses that describe a transition IN FLIGHT. Only these ever animate, and only
90
+ // while the run driving the transition is still being driven itself.
91
+ const envInTransition = computed(
92
+ () =>
93
+ props.environment?.status === 'provisioning' || props.environment?.status === 'tearing_down',
94
+ )
77
95
  </script>
78
96
 
79
97
  <template>
@@ -88,10 +106,7 @@ const ENV_STATUS_META = computed<
88
106
  class="h-3.5 w-3.5"
89
107
  :class="[
90
108
  ENV_STATUS_META[environment.status].color,
91
- {
92
- 'animate-spin':
93
- environment.status === 'provisioning' || environment.status === 'tearing_down',
94
- },
109
+ { 'animate-spin': runActive && envInTransition },
95
110
  ]"
96
111
  />
97
112
  <span :class="ENV_STATUS_META[environment.status].color">{{
@@ -25,6 +25,7 @@ import {
25
25
  REDIRECT_PARK_PRESENTATION,
26
26
  type RedirectParkView,
27
27
  dedicatedParkView,
28
+ runIsActive,
28
29
  } from '~/utils/pipelineRender'
29
30
  import InputGateNotice from '~/components/inputGate/InputGateNotice.vue'
30
31
 
@@ -125,13 +126,12 @@ const showHistory = ref(false)
125
126
  // "spinning up" phase, no spinner.
126
127
  const runFailed = computed(() => instance.value?.status === 'failed')
127
128
 
128
- // Whether the run is still doing something (can still spin infra up/down). A terminal
129
- // run (`done`/`failed`) has nothing left to provision, so the infra-attempts drawer
130
- // stops its background live-polling (manual refresh stays available).
131
- const runLive = computed(() => {
132
- const status = instance.value?.status
133
- return status != null && status !== 'done' && status !== 'failed'
134
- })
129
+ // Whether the engine is still driving this run, and so can still spin infrastructure up or down.
130
+ // One shared predicate drives every infra surface below: the attempts drawer's background poll
131
+ // (manual refresh stays available regardless), the container card's cold-boot spinner, and the
132
+ // environment panel's transition spinner. A run that is terminal OR parked has nothing in flight,
133
+ // so none of those may keep animating.
134
+ const runActive = computed(() => runIsActive(instance.value?.status))
135
135
 
136
136
  // Live elapsed-time clock for the open step.
137
137
  const { isRunning, durationLabel } = useStepTimer({
@@ -497,6 +497,7 @@ async function copyOutput() {
497
497
  <StepMetadataCard
498
498
  :step="step"
499
499
  :run-failed="runFailed"
500
+ :run-active="runActive"
500
501
  :duration-label="durationLabel"
501
502
  :is-running="isRunning"
502
503
  :step-number="stepNumber"
@@ -592,7 +593,11 @@ async function copyOutput() {
592
593
 
593
594
  <!-- ephemeral environment lifecycle (spinning up / running / shut down /
594
595
  errored + the exact error), when this step runs against one -->
595
- <EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
596
+ <EnvironmentStatusPanel
597
+ v-if="stepEnvironment"
598
+ :environment="stepEnvironment"
599
+ :run-active="runActive"
600
+ />
596
601
 
597
602
  <!-- frontend UI-test: how the frame's backend bindings resolved (env var →
598
603
  live URL | mocked) + the run-start advisories (duplicate env vars /
@@ -639,7 +644,7 @@ async function copyOutput() {
639
644
  v-if="showProvisioning"
640
645
  class="mt-2"
641
646
  :execution-id="executionId"
642
- :live="runLive"
647
+ :live="runActive"
643
648
  />
644
649
  </div>
645
650
 
@@ -8,7 +8,18 @@ import { containerPhaseLabel } from '~/utils/pipelineRender'
8
8
  // making calls), and the container's id + reachable URL once up. Shared by the generic
9
9
  // step detail (StepMetadataCard) and the dedicated Tester window so both surface WHAT the
10
10
  // container is doing and WHERE it lives instead of a bare "working" — identical parity.
11
- const props = defineProps<{ step: PipelineStep; runFailed: boolean }>()
11
+ const props = defineProps<{
12
+ step: PipelineStep
13
+ runFailed: boolean
14
+ /**
15
+ * Whether the enclosing run is still being driven (`runIsActive`). Distinct from
16
+ * {@link runFailed}, which answers whether the container was RECLAIMED: a run parked on a
17
+ * human decision or a spend budget has reclaimed nothing, so its container keeps whatever
18
+ * status it stopped at. A `starting` one is no longer cold-booting, though, so it must not
19
+ * keep spinning as though it were.
20
+ */
21
+ runActive: boolean
22
+ }>()
12
23
 
13
24
  const { t, te } = useI18n()
14
25
 
@@ -59,6 +70,14 @@ const CONTAINER_STATUS_META: Record<
59
70
  },
60
71
  }
61
72
 
73
+ // Whether the status icon animates: only a cold-boot genuinely in flight, i.e. one whose run is
74
+ // still being driven. A `starting` container on a parked or terminated run keeps its label and
75
+ // freezes, the same way a mid-flight subtask item does (`subtaskIconClass`).
76
+ const spinIcon = computed(
77
+ () =>
78
+ props.runActive && !!containerStatus.value && CONTAINER_STATUS_META[containerStatus.value].spin,
79
+ )
80
+
62
81
  // The friendly phase label (clone → "Preparing workspace", …); only meaningful while up.
63
82
  const phaseLabel = computed(() => containerPhaseLabel(props.step.container?.phase, { t, te }))
64
83
 
@@ -82,7 +101,7 @@ const { copy: copyText } = useCopyToClipboard()
82
101
  <UIcon
83
102
  :name="CONTAINER_STATUS_META[containerStatus].icon"
84
103
  class="h-4 w-4 shrink-0"
85
- :class="CONTAINER_STATUS_META[containerStatus].spin ? 'animate-spin' : ''"
104
+ :class="spinIcon ? 'animate-spin' : ''"
86
105
  />
87
106
  <span class="font-medium">{{ t(CONTAINER_STATUS_KEYS[containerStatus]) }}</span>
88
107
  <template v-if="phaseLabel && containerStatus === 'up'">
@@ -20,6 +20,8 @@ import MarkdownProse from '~/components/common/MarkdownProse.vue'
20
20
  const props = defineProps<{
21
21
  step: PipelineStep
22
22
  runFailed: boolean
23
+ /** Whether the run is still being driven; passed through so its container card can freeze. */
24
+ runActive: boolean
23
25
  durationLabel: string | null
24
26
  isRunning: boolean
25
27
  stepNumber: number
@@ -246,7 +248,12 @@ async function copyRunId() {
246
248
 
247
249
  <!-- container lifecycle (status / live phase / id + url) — shared with the Tester
248
250
  window so both surface what the container is doing and where it lives. -->
249
- <StepContainerStatus :step="step" :run-failed="runFailed" class="mt-4" />
251
+ <StepContainerStatus
252
+ :step="step"
253
+ :run-failed="runFailed"
254
+ :run-active="runActive"
255
+ class="mt-4"
256
+ />
250
257
 
251
258
  <!-- live subtask breakdown -->
252
259
  <div v-if="step.subtasks && step.subtasks.total > 0" class="mt-4">
@@ -6,13 +6,16 @@
6
6
  // panels' drawer, or `executionId` for a run's "Infrastructure attempts" drawer (which
7
7
  // surfaces that run's container/runner/env attempts).
8
8
  //
9
- // In `executionId` mode the drawer LIVE-tracks: while the run is active (`live`) it
10
- // silently re-polls so each container spin-up / tear-down appears with its timestamp as
11
- // it happens, and it does one final poll when the run goes terminal to catch the last
12
- // tear-down row (written just before the terminal event), after which the auto-poll
13
- // stops. Background polls never spin the refresh button (they're silent), but the manual
14
- // refresh control stays available even once the run is terminal so a tear-down row that
15
- // was missed or not yet persisted at the terminal instant can always be refetched.
9
+ // In `executionId` mode the drawer LIVE-tracks: while the engine is driving the run (`live`,
10
+ // from `runIsActive`) it silently re-polls so each container spin-up / tear-down appears with
11
+ // its timestamp as it happens, and it does one final poll when the run stops being driven, to
12
+ // catch the last tear-down row (written just before the terminal event), after which the
13
+ // auto-poll stops. "Stops being driven" covers a PARK as well as a terminal state: a run waiting
14
+ // on a human decision or a raised spend budget writes no further attempts until it resumes, and
15
+ // the same watch restarts the poll when it does. Background polls never spin the refresh button
16
+ // (they're silent), but the manual refresh control stays available even once the run is terminal,
17
+ // so a tear-down row that was missed or not yet persisted at the terminal instant can always be
18
+ // refetched.
16
19
  import { onBeforeUnmount, onMounted, watch } from 'vue'
17
20
  import type {
18
21
  ProvisioningOperation,
@@ -23,7 +26,10 @@ import type {
23
26
  const props = defineProps<{
24
27
  subsystem?: ProvisioningSubsystem
25
28
  executionId?: string
26
- /** Run-details mode only: whether the run is still active (drives live polling). */
29
+ /**
30
+ * Run-details mode only: whether the engine is still driving the run (`runIsActive`), which
31
+ * drives the background poll. A terminal OR parked run writes no further attempts.
32
+ */
27
33
  live?: boolean
28
34
  }>()
29
35
 
@@ -68,8 +74,8 @@ watch(
68
74
  // clears, `live` and `executionId` fall away in the same tick, so stop the interval
69
75
  // unconditionally or it leaks (firing no-op reloads) for the component's lifetime.
70
76
  stopPolling()
71
- // On the active→terminal transition, poll once more (silently) to pick up the
72
- // tear-down row the engine writes just before it emits the terminal state.
77
+ // On the active→stopped transition (parked or terminal), poll once more (silently) to pick
78
+ // up the tear-down row the engine writes just before it emits that state.
73
79
  if (wasLive && props.executionId != null) reload(true)
74
80
  },
75
81
  { immediate: true },
@@ -21,6 +21,7 @@ import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
21
21
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
22
22
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
23
23
  import MarkdownProse from '~/components/common/MarkdownProse.vue'
24
+ import { runIsActive } from '~/utils/pipelineRender'
24
25
 
25
26
  const board = useBoardStore()
26
27
  const execution = useExecutionStore()
@@ -65,12 +66,10 @@ const qualityVerdicts = computed(() => [...(quality.value?.verdicts ?? [])].reve
65
66
  // run's infrastructure attempts + logs (container/runner/env spin-up), not just the
66
67
  // report. The container/subtask signals already flow onto the step via the generic poll.
67
68
  const runFailed = computed(() => instance.value?.status === 'failed')
68
- // A terminal run (done/failed) can't spin more infra: the attempts drawer stops its
69
- // background live-polling (manual refresh stays available).
70
- const runLive = computed(() => {
71
- const status = instance.value?.status
72
- return status != null && status !== 'done' && status !== 'failed'
73
- })
69
+ // Whether the engine is still driving this run. A run that is terminal OR parked can't spin more
70
+ // infra, so the attempts drawer stops its background poll (manual refresh stays available) and
71
+ // neither the container card nor the environment panel keeps animating over it.
72
+ const runActive = computed(() => runIsActive(instance.value?.status))
74
73
  const stepEnvironment = computed(() => step.value?.environment ?? null)
75
74
  const executionId = computed(() => instance.value?.id ?? null)
76
75
  // The infra-attempts log drawer is opened on demand (it fetches the per-run log rows).
@@ -355,8 +354,12 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
355
354
  <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
356
355
  {{ t('testing.infrastructure') }}
357
356
  </h3>
358
- <StepContainerStatus :step="step" :run-failed="runFailed" />
359
- <EnvironmentStatusPanel v-if="stepEnvironment" :environment="stepEnvironment" />
357
+ <StepContainerStatus :step="step" :run-failed="runFailed" :run-active="runActive" />
358
+ <EnvironmentStatusPanel
359
+ v-if="stepEnvironment"
360
+ :environment="stepEnvironment"
361
+ :run-active="runActive"
362
+ />
360
363
 
361
364
  <!-- In-container docker-compose dependency stand-up (local-infra tester): the
362
365
  outcome + the captured `docker compose up` logs. This is the stand-up that
@@ -459,7 +462,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
459
462
  v-if="showProvisioning"
460
463
  class="mt-2"
461
464
  :execution-id="executionId"
462
- :live="runLive"
465
+ :live="runActive"
463
466
  />
464
467
  </div>
465
468
  </section>
@@ -115,6 +115,7 @@ export type {
115
115
  VisualConfirmDesignReferences,
116
116
  VisualConfirmRound,
117
117
  ExecutionInstance,
118
+ ExecutionStatus,
118
119
  // The historical frontend name for a per-block review comment is the contract's
119
120
  // StepReviewComment; the env-status union is the contract's EnvironmentStatus.
120
121
  StepReviewComment as ReviewComment,
@@ -26,6 +26,7 @@ export const FAILURE_KIND_KEYS: Record<AgentFailureKind, string> = {
26
26
  dispatch: 'platformObservability.failureKind.dispatch',
27
27
  environment: 'platformObservability.failureKind.environment',
28
28
  evicted: 'platformObservability.failureKind.evicted',
29
+ harness_shutdown: 'platformObservability.failureKind.harness_shutdown',
29
30
  timeout: 'platformObservability.failureKind.timeout',
30
31
  agent: 'platformObservability.failureKind.agent',
31
32
  job_failed: 'platformObservability.failureKind.job_failed',
@@ -1,8 +1,17 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { binaryCandidateStatusSchema, stepSkipReasonSchema } from '@cat-factory/contracts'
2
+ import {
3
+ binaryCandidateStatusSchema,
4
+ executionStatusSchema,
5
+ stepSkipReasonSchema,
6
+ } from '@cat-factory/contracts'
3
7
  import type { ExecutionInstance, PipelineStep } from '~/types/execution'
4
8
  import { missingI18nKeys } from '../../test/i18nKeys'
5
- import { REDIRECT_PARK_PRESENTATION, dedicatedParkView, stepSkipReasonKey } from './pipelineRender'
9
+ import {
10
+ REDIRECT_PARK_PRESENTATION,
11
+ dedicatedParkView,
12
+ runIsActive,
13
+ stepSkipReasonKey,
14
+ } from './pipelineRender'
6
15
 
7
16
  /** A minimal coder step; the predicate only reads approval/followUps/forkDecision. */
8
17
  const step = (over: Partial<PipelineStep>): PipelineStep =>
@@ -229,3 +238,29 @@ describe('stepSkipReasonKey', () => {
229
238
  expect(missingI18nKeys([...keys, 'pipeline.progress.skipped.unknown'])).toEqual([])
230
239
  })
231
240
  })
241
+
242
+ describe('runIsActive', () => {
243
+ it('answers true for exactly the one status the engine is driving under', () => {
244
+ // Derived from the vocabulary the engine writes, not a hand-listed set: a status added to the
245
+ // picklist fails here until someone decides whether infrastructure can move under it. That
246
+ // decision is what every animated infra indicator is gated on, so defaulting a new member
247
+ // either way silently is the failure this pins.
248
+ expect(executionStatusSchema.options.filter(runIsActive)).toEqual(['running'])
249
+ })
250
+
251
+ it('treats a PARK as inactive, not only a terminal state', () => {
252
+ // The distinction that makes this more than `status !== 'done' && status !== 'failed'`: a run
253
+ // waiting on a human decision (`blocked`) or on a raised spend budget (`paused`) is asleep in
254
+ // the durable driver. Nothing is cold-booting or being torn down under it, so a container left
255
+ // `starting` or an environment left `provisioning` must stop claiming otherwise.
256
+ expect(runIsActive('blocked')).toBe(false)
257
+ expect(runIsActive('paused')).toBe(false)
258
+ })
259
+
260
+ it('answers false when the run has no status to read', () => {
261
+ // The overlays render before/after their run resolves, and an unknown run is not a running
262
+ // one: the indicator must start still and begin turning once the run says it is being driven.
263
+ expect(runIsActive(null)).toBe(false)
264
+ expect(runIsActive(undefined)).toBe(false)
265
+ })
266
+ })
@@ -2,9 +2,38 @@
2
2
  // TaskPipelineMini, AgentStepDetail), so the "is this step still live?" logic stays
3
3
  // in one place rather than being re-derived as inline ternaries per component.
4
4
 
5
- import type { AgentState, ExecutionInstance, PipelineStep } from '~/types/execution'
5
+ import type {
6
+ AgentState,
7
+ ExecutionInstance,
8
+ ExecutionStatus,
9
+ PipelineStep,
10
+ } from '~/types/execution'
6
11
  import { isStepSkipReason } from '@cat-factory/contracts'
7
12
 
13
+ /**
14
+ * Whether the engine is presently DRIVING this run, which is the one condition under which
15
+ * infrastructure can still be moving (a container cold-booting, an environment coming up or
16
+ * being torn down, a fresh provisioning attempt landing in the log).
17
+ *
18
+ * `running` is that condition and nothing else is. `done`/`failed` are terminal, and the other
19
+ * two are parks where the durable driver is asleep on an event: `blocked` waits on a human
20
+ * decision, `paused` on a spend budget that has to be raised. A parked run holds whatever infra
21
+ * state it stopped at, so nothing about it is in flight.
22
+ *
23
+ * This is what every ANIMATED infra indicator is gated on. A spinner is a claim that something is
24
+ * happening right now, so a container left `starting` or an environment left `provisioning` when
25
+ * its run stopped must keep its label (that IS the last thing the provider reported) and stop
26
+ * turning: the run's own status says why it stopped, and a perpetual spinner over a run nobody is
27
+ * driving reads as a live cold-boot that is simply taking a while. The same rule governs the
28
+ * infra-attempts drawer's background poll, which has nothing to re-read off a parked run.
29
+ *
30
+ * The step-level sibling is `stepIsRunning` (`useStepTimer`), which answers the narrower question
31
+ * of whether one step's clock should tick; this one is about the run as a whole.
32
+ */
33
+ export function runIsActive(status: ExecutionStatus | null | undefined): boolean {
34
+ return status === 'running'
35
+ }
36
+
8
37
  /**
9
38
  * Visual state of a conditionally-run companion attached to a gate step (today the
10
39
  * Tester's `fixer`): it MIGHT run (`possible`), is running now (`running`), ran at
@@ -3848,6 +3848,7 @@
3848
3848
  "dispatch": "Zustellung",
3849
3849
  "environment": "Umgebung",
3850
3850
  "evicted": "Verdrängt",
3851
+ "harness_shutdown": "Harness beendet",
3851
3852
  "timeout": "Zeitüberschreitung",
3852
3853
  "agent": "Agent",
3853
3854
  "job_failed": "Job fehlgeschlagen",
@@ -1892,6 +1892,7 @@
1892
1892
  "dispatch": "Dispatch",
1893
1893
  "environment": "Environment",
1894
1894
  "evicted": "Evicted",
1895
+ "harness_shutdown": "Harness shut down",
1895
1896
  "timeout": "Timeout",
1896
1897
  "agent": "Agent",
1897
1898
  "job_failed": "Job failed",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "Despacho",
1795
1795
  "environment": "Entorno",
1796
1796
  "evicted": "Desalojada",
1797
+ "harness_shutdown": "Harness detenido",
1797
1798
  "timeout": "Tiempo agotado",
1798
1799
  "agent": "Agente",
1799
1800
  "job_failed": "Trabajo fallido",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "Envoi",
1795
1795
  "environment": "Environnement",
1796
1796
  "evicted": "Évincée",
1797
+ "harness_shutdown": "Harness arrêté",
1797
1798
  "timeout": "Délai dépassé",
1798
1799
  "agent": "Agent",
1799
1800
  "job_failed": "Tâche échouée",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "שיגור",
1795
1795
  "environment": "סביבה",
1796
1796
  "evicted": "פונתה",
1797
+ "harness_shutdown": "כיבוי ה-Harness",
1797
1798
  "timeout": "פסק זמן",
1798
1799
  "agent": "סוכן",
1799
1800
  "job_failed": "המשימה נכשלה",
@@ -3848,6 +3848,7 @@
3848
3848
  "dispatch": "Invio",
3849
3849
  "environment": "Ambiente",
3850
3850
  "evicted": "Sfrattata",
3851
+ "harness_shutdown": "Harness arrestato",
3851
3852
  "timeout": "Timeout",
3852
3853
  "agent": "Agente",
3853
3854
  "job_failed": "Job fallito",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "ディスパッチ",
1795
1795
  "environment": "環境",
1796
1796
  "evicted": "退避",
1797
+ "harness_shutdown": "Harness 停止",
1797
1798
  "timeout": "タイムアウト",
1798
1799
  "agent": "エージェント",
1799
1800
  "job_failed": "ジョブ失敗",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "Wysłanie",
1795
1795
  "environment": "Środowisko",
1796
1796
  "evicted": "Usunięte",
1797
+ "harness_shutdown": "Harness zatrzymany",
1797
1798
  "timeout": "Przekroczono czas",
1798
1799
  "agent": "Agent",
1799
1800
  "job_failed": "Zadanie nieudane",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "Gönderim",
1795
1795
  "environment": "Ortam",
1796
1796
  "evicted": "Tahliye edildi",
1797
+ "harness_shutdown": "Harness kapatıldı",
1797
1798
  "timeout": "Zaman aşımı",
1798
1799
  "agent": "Aracı",
1799
1800
  "job_failed": "İş başarısız",
@@ -1794,6 +1794,7 @@
1794
1794
  "dispatch": "Відправлення",
1795
1795
  "environment": "Середовище",
1796
1796
  "evicted": "Витіснено",
1797
+ "harness_shutdown": "Harness зупинено",
1797
1798
  "timeout": "Час вичерпано",
1798
1799
  "agent": "Агент",
1799
1800
  "job_failed": "Завдання не виконано",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.272.0",
3
+ "version": "0.273.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",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.41",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.311.0"
43
+ "@cat-factory/contracts": "0.312.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",