@cat-factory/app 0.141.2 → 0.142.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.
@@ -289,8 +289,9 @@ const selectedModelPresetLabel = computed(() => {
289
289
  // Hide UI-testing pipelines (`tester-ui` / `visual-confirmation`) when the target frame has no
290
290
  // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate). Also
291
291
  // hide `'recurring'`-only pipelines (a one-off task start of one is refused at run start) and,
292
- // for a `document` task, every non-document pipeline (it authors a doc only document pipelines
293
- // are relevant, per the `purpose` classifier). Re-filters as the chosen task type changes.
292
+ // for a `document` / `review` task, every pipeline whose purpose doesn't match (a doc task authors
293
+ // a doc, a review task reviews a PR only document / review pipelines are relevant, per the
294
+ // `purpose` classifier). Re-filters as the chosen task type changes.
294
295
  const selectablePipelines = computed(() =>
295
296
  pipelines.pipelines.filter((p) =>
296
297
  pipelineAllowedForManualStart(p, frame.value, board.blocks, taskType.value),
@@ -302,14 +303,16 @@ const selectablePipelines = computed(() =>
302
303
  // must appear in the form BEFORE creation:
303
304
  // - `ralph` needs its preset so the per-task validation command + iteration budget the `ralph`
304
305
  // agent contributes surface for editing ("choose at run time" would be a dead end);
305
- // - a `document` task defaults to `pl_document` so its document-only picker (the `purpose` gate
306
- // hides every non-document pipeline) is never rendered empty.
307
- // The other typed defaults (spike/review) carry no up-front config and don't narrow their picker,
308
- // so the modal leaves `pipelineId` unset and `BoardService` applies the backend type-default at
309
- // creation. Keep these ids in step with the backend helper.
306
+ // - a `document` task defaults to `pl_document` and a `review` task to `pl_review` so their
307
+ // purpose-narrowed picker (the `purpose` gate hides every non-document / non-review pipeline)
308
+ // is never rendered empty.
309
+ // The other typed default (spike) carries no up-front config and doesn't narrow its picker, so the
310
+ // modal leaves `pipelineId` unset and `BoardService` applies the backend type-default at creation.
311
+ // Keep these ids in step with the backend helper.
310
312
  const DEFAULT_PIPELINE_FOR_TYPE: Partial<Record<TaskTypeChoice, string>> = {
311
313
  ralph: 'pl_ralph',
312
314
  document: 'pl_document',
315
+ review: 'pl_review',
313
316
  }
314
317
  watch(taskType, (next) => {
315
318
  const preset = DEFAULT_PIPELINE_FOR_TYPE[next]
@@ -282,7 +282,7 @@ async function linkDocumentFragment() {
282
282
  // error surfaced) so a retry re-attempts just those, never re-linking what already went in.
283
283
  let linked = 0
284
284
  let firstError: unknown
285
- for (const { path, ref } of [...stagedDocRefs.value]) {
285
+ for (const { path, ref } of stagedDocRefs.value) {
286
286
  try {
287
287
  await library.createDocumentFragment({ source, ref, tags })
288
288
  const i = docFilePaths.value.indexOf(path)
@@ -1,13 +1,16 @@
1
1
  <script setup lang="ts">
2
2
  // Startup advisory for unhealthy pipelines. Opened once per session from the board page when
3
3
  // `usePipelineHealth` reports any issue. Lists:
4
+ // • new built-in pipelines the workspace doesn't have yet (ADD them);
4
5
  // • invalid pipelines (unknown agent kind / bad shape) — DELETE a custom one, RESEED a built-in;
5
6
  // • outdated built-ins (a newer catalog definition is available) — RESEED to adopt it.
6
- // Detection is client-side (see usePipelineHealth); the actions hit the pipelines store.
7
+ // Adding a new built-in and reseeding an existing one are the same reseed call (it creates or
8
+ // updates by catalog id). Detection is client-side (see usePipelineHealth); the actions hit the
9
+ // pipelines store.
7
10
  const { t } = useI18n()
8
11
  const ui = useUiStore()
9
12
  const pipelines = usePipelinesStore()
10
- const { invalid, outdated, hasIssues } = usePipelineHealth()
13
+ const { invalid, outdated, newPipelines, hasIssues } = usePipelineHealth()
11
14
  const toast = useToast()
12
15
 
13
16
  const open = computed({
@@ -45,17 +48,20 @@ const reseed = (id: string) =>
45
48
  const remove = (id: string) =>
46
49
  run(id, () => pipelines.removePipeline(id), t('pipeline.health.toast.deleteFailed'))
47
50
 
48
- /** Reseed every reseedable pipeline (outdated built-ins + invalid built-ins) in one go. */
51
+ /** Reseed every reseedable pipeline (new + outdated built-ins + invalid built-ins) in one go. */
49
52
  async function reseedAll() {
50
- const ids = [...invalid.value.filter((h) => h.pipeline.builtin), ...outdated.value].map(
51
- (h) => h.pipeline.id,
52
- )
53
+ const ids = [
54
+ ...newPipelines.value.map((p) => p.id),
55
+ ...invalid.value.filter((h) => h.pipeline.builtin).map((h) => h.pipeline.id),
56
+ ...outdated.value.map((h) => h.pipeline.id),
57
+ ]
53
58
  for (const id of new Set(ids)) await reseed(id)
54
59
  }
55
60
 
56
61
  const reseedableCount = computed(
57
62
  () =>
58
63
  new Set([
64
+ ...newPipelines.value.map((p) => p.id),
59
65
  ...invalid.value.filter((h) => h.pipeline.builtin).map((h) => h.pipeline.id),
60
66
  ...outdated.value.map((h) => h.pipeline.id),
61
67
  ]).size,
@@ -71,6 +77,41 @@ const reseedableCount = computed(
71
77
  </div>
72
78
 
73
79
  <div v-else class="space-y-5">
80
+ <!-- New built-in pipelines the workspace can add. -->
81
+ <section v-if="newPipelines.length" class="space-y-2">
82
+ <div class="flex items-center gap-2">
83
+ <UIcon name="i-lucide-sparkles" class="h-4 w-4 text-emerald-400" />
84
+ <h3 class="text-sm font-semibold text-slate-200">
85
+ {{ t('pipeline.health.newHeading') }}
86
+ </h3>
87
+ </div>
88
+ <p class="text-[11px] text-slate-500">{{ t('pipeline.health.newDescription') }}</p>
89
+ <ul class="space-y-2">
90
+ <li
91
+ v-for="p in newPipelines"
92
+ :key="p.id"
93
+ class="flex items-center justify-between gap-3 rounded-lg border border-slate-800 bg-slate-900/40 p-3"
94
+ >
95
+ <div class="min-w-0">
96
+ <span class="truncate text-sm font-medium text-slate-100 capitalize">{{
97
+ p.name
98
+ }}</span>
99
+ </div>
100
+ <UButton
101
+ size="xs"
102
+ color="primary"
103
+ variant="subtle"
104
+ icon="i-lucide-plus"
105
+ :loading="isBusy(p.id)"
106
+ :disabled="anyBusy"
107
+ @click="reseed(p.id)"
108
+ >
109
+ {{ t('pipeline.health.add') }}
110
+ </UButton>
111
+ </li>
112
+ </ul>
113
+ </section>
114
+
74
115
  <!-- Invalid: unknown agent kinds or a broken shape. -->
75
116
  <section v-if="invalid.length" class="space-y-2">
76
117
  <div class="flex items-center gap-2">
@@ -142,4 +142,26 @@ describe('usePipelineHealth', () => {
142
142
  expect(invalid.value).toHaveLength(1)
143
143
  expect(outdated.value).toHaveLength(0)
144
144
  })
145
+
146
+ it('surfaces a brand-new built-in the workspace does not have yet (offer to add)', () => {
147
+ // A board seeded before `pl_review` shipped: the catalog versions advertise it, but no stored
148
+ // pipeline has that id — so it must appear as a "new" pipeline the user can add.
149
+ const stored = builtin(['coder', 'reviewer'], { id: 'pl_full', version: 1 })
150
+ const { newPipelines, hasIssues, invalid, outdated } = scan([stored], {
151
+ pl_full: 1,
152
+ pl_review: 4,
153
+ })
154
+ expect(newPipelines.value).toEqual([{ id: 'pl_review', name: 'review' }])
155
+ expect(hasIssues.value).toBe(true)
156
+ // A brand-new built-in is not "invalid" or "outdated" — those only concern STORED pipelines.
157
+ expect(invalid.value).toHaveLength(0)
158
+ expect(outdated.value).toHaveLength(0)
159
+ })
160
+
161
+ it('reports no new pipelines when every catalog id is already stored', () => {
162
+ const stored = builtin(['coder', 'reviewer'], { id: 'pl_full', version: 1 })
163
+ const { newPipelines, hasIssues } = scan([stored], { pl_full: 1 })
164
+ expect(newPipelines.value).toHaveLength(0)
165
+ expect(hasIssues.value).toBe(false)
166
+ })
145
167
  })
@@ -23,6 +23,23 @@ export interface PipelineHealth {
23
23
  outdated: boolean
24
24
  }
25
25
 
26
+ /** A brand-new built-in pipeline that appeared in the catalog but isn't in the workspace yet. */
27
+ export interface NewPipeline {
28
+ /** The catalog (built-in) id — what the reseed endpoint is keyed by (it creates the row). */
29
+ id: string
30
+ /** The built-in's display name, from the catalog versions' companion name map. */
31
+ name: string
32
+ }
33
+
34
+ /**
35
+ * A built-in's display name for the "new pipeline" advisory, humanised from its catalog id
36
+ * (`pl_review` -> "review", rendered capitalised) — used only until the row is reseeded into
37
+ * existence, at which point its real catalog name is stored. Mirrors `useRiskPolicyHealth`.
38
+ */
39
+ function builtinPipelineName(id: string): string {
40
+ return id.replace(/^pl_/, '').replace(/_/g, ' ')
41
+ }
42
+
26
43
  /** Producers a companion kind is allowed to review (inverse of {@link COMPANION_FOR_PRODUCER}). */
27
44
  function companionTargets(companion: string): string[] {
28
45
  return Object.entries(COMPANION_FOR_PRODUCER)
@@ -88,12 +105,14 @@ function shapeProblem(p: Pipeline): string | null {
88
105
 
89
106
  /**
90
107
  * Detect pipelines in an unhealthy state for the startup advisory: those referencing an unknown
91
- * agent kind or with an invalid shape (offer to delete a custom one / reseed a built-in), and
92
- * built-ins whose seeded definition has moved ahead of the stored copy (offer to reseed). Reads
93
- * the pipeline library + the snapshot's catalog versions from the pipelines store. Detection runs
94
- * entirely client-side because the canonical agent-kind catalog lives here (`AGENT_BY_KIND` +
95
- * `SYSTEM_AGENT_META` + registered custom kinds); the version comparison uses the catalog
96
- * versions the snapshot ships.
108
+ * agent kind or with an invalid shape (offer to delete a custom one / reseed a built-in), built-ins
109
+ * whose seeded definition has moved ahead of the stored copy (offer to reseed), AND brand-new
110
+ * built-ins that appeared in the catalog but aren't in the workspace yet (offer to ADD them — a
111
+ * board seeded before the built-in shipped, e.g. `pl_review`). Reads the pipeline library + the
112
+ * snapshot's catalog versions from the pipelines store. Detection runs entirely client-side: the
113
+ * canonical agent-kind catalog lives here (`AGENT_BY_KIND` + `SYSTEM_AGENT_META` + registered custom
114
+ * kinds), and the catalog versions the snapshot ships ARE the set of built-in ids — a catalog id
115
+ * with no stored pipeline is a new built-in. Mirrors `useRiskPolicyHealth` / `useModelPresetHealth`.
97
116
  */
98
117
  export function usePipelineHealth() {
99
118
  const store = usePipelinesStore()
@@ -130,11 +149,20 @@ export function usePipelineHealth() {
130
149
  return out
131
150
  })
132
151
 
152
+ // Brand-new built-ins: a catalog id (a `catalogVersions` key) with no stored pipeline. Adding one
153
+ // is the same reseed call as adopting an update (it inserts the row when absent).
154
+ const newPipelines = computed<NewPipeline[]>(() => {
155
+ const storedIds = new Set(store.pipelines.map((p) => p.id))
156
+ return Object.keys(store.catalogVersions)
157
+ .filter((id) => !storedIds.has(id))
158
+ .map((id) => ({ id, name: builtinPipelineName(id) }))
159
+ })
160
+
133
161
  // An invalid built-in is reseeded (not deleted) and that also clears any "outdated" flag, so
134
162
  // exclude it from the outdated list to avoid offering the same fix twice.
135
163
  const invalid = computed(() => health.value.filter((h) => h.invalid))
136
164
  const outdated = computed(() => health.value.filter((h) => h.outdated && !h.invalid))
137
- const hasIssues = computed(() => health.value.length > 0)
165
+ const hasIssues = computed(() => health.value.length > 0 || newPipelines.value.length > 0)
138
166
 
139
- return { health, invalid, outdated, hasIssues }
167
+ return { health, invalid, outdated, newPipelines, hasIssues }
140
168
  }
@@ -17,10 +17,19 @@ describe('pipelineAllowedForTaskType', () => {
17
17
  expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), 'document')).toBe(false)
18
18
  })
19
19
 
20
- it('every non-document task type is unrestricted (any purpose, and undefined type)', () => {
21
- for (const type of ['feature', 'bug', 'spike', 'review', 'ralph', undefined] as const) {
20
+ it('a review task offers ONLY review-purpose pipelines', () => {
21
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'review' }), 'review')).toBe(true)
22
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'review')).toBe(false)
23
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), 'review')).toBe(false)
24
+ // An unclassified pipeline is hidden from a review task (it requires the explicit classifier).
25
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), 'review')).toBe(false)
26
+ })
27
+
28
+ it('every other task type is unrestricted (any purpose, and undefined type)', () => {
29
+ for (const type of ['feature', 'bug', 'spike', 'ralph', undefined] as const) {
22
30
  expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), type)).toBe(true)
23
31
  expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), type)).toBe(true)
32
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'review' }), type)).toBe(true)
24
33
  expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), type)).toBe(true)
25
34
  }
26
35
  })
@@ -51,10 +60,15 @@ describe('pipelineAllowedForManualStart composes the task-type gate', () => {
51
60
  const noFrame = undefined
52
61
  const blocks: Block[] = []
53
62
 
54
- it('drops a non-document pipeline for a document task, keeps it for others', () => {
63
+ it('drops a mismatched pipeline for a document / review task, keeps it for others', () => {
55
64
  const build = pipeline({ purpose: 'build' })
56
65
  expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'document')).toBe(false)
66
+ expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'review')).toBe(false)
57
67
  expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'feature')).toBe(true)
68
+ // A review pipeline is offered to a review task and hidden from a document task.
69
+ const review = pipeline({ purpose: 'review' })
70
+ expect(pipelineAllowedForManualStart(review, noFrame, blocks, 'review')).toBe(true)
71
+ expect(pipelineAllowedForManualStart(review, noFrame, blocks, 'document')).toBe(false)
58
72
  // No task type supplied ⇒ no task-type restriction.
59
73
  expect(pipelineAllowedForManualStart(build, noFrame, blocks)).toBe(true)
60
74
  })
@@ -3260,6 +3260,9 @@
3260
3260
  "invalidHeading": "Ungültige Pipelines",
3261
3261
  "invalidDescription": "Diese verweisen auf einen fehlenden Agenten oder sind falsch konfiguriert, sodass sie beim Start fehlschlagen (oder falsch laufen) würden. Löschen Sie eine benutzerdefinierte Pipeline oder setzen Sie eine integrierte neu auf, um ihre Katalogdefinition wiederherzustellen.",
3262
3262
  "builtinBadge": "integriert",
3263
+ "newHeading": "Neue Pipelines verfügbar",
3264
+ "newDescription": "Neue integrierte Pipelines sind verfügbar. Füge sie zur Bibliothek dieses Boards hinzu.",
3265
+ "add": "Hinzufügen",
3263
3266
  "reseed": "Neu aufsetzen",
3264
3267
  "delete": "Löschen",
3265
3268
  "updatesHeading": "Updates verfügbar",
@@ -3626,6 +3626,9 @@
3626
3626
  "invalidHeading": "Invalid pipelines",
3627
3627
  "invalidDescription": "These reference a missing agent or are misconfigured, so they would fail (or misrun) at start. Delete a custom pipeline, or reseed a built-in to restore its catalog definition.",
3628
3628
  "builtinBadge": "built-in",
3629
+ "newHeading": "New pipelines available",
3630
+ "newDescription": "New built-in pipelines have shipped. Add them to this board's library.",
3631
+ "add": "Add",
3629
3632
  "reseed": "Reseed",
3630
3633
  "delete": "Delete",
3631
3634
  "updatesHeading": "Updates available",
@@ -3527,6 +3527,9 @@
3527
3527
  "invalidHeading": "Pipelines no válidos",
3528
3528
  "invalidDescription": "Estos hacen referencia a un agente inexistente o están mal configurados, por lo que fallarían (o se ejecutarían mal) al iniciar. Elimina un pipeline personalizado o vuelve a generar uno integrado para restaurar su definición del catálogo.",
3529
3529
  "builtinBadge": "integrado",
3530
+ "newHeading": "Nuevos pipelines disponibles",
3531
+ "newDescription": "Hay nuevos pipelines integrados. Añádelos a la biblioteca de este tablero.",
3532
+ "add": "Añadir",
3530
3533
  "reseed": "Regenerar",
3531
3534
  "delete": "Eliminar",
3532
3535
  "updatesHeading": "Actualizaciones disponibles",
@@ -3527,6 +3527,9 @@
3527
3527
  "invalidHeading": "Pipelines non valides",
3528
3528
  "invalidDescription": "Ceux-ci référencent un agent manquant ou sont mal configurés et échoueraient (ou s'exécuteraient mal) au démarrage. Supprimez un pipeline personnalisé, ou régénérez un pipeline intégré pour restaurer sa définition du catalogue.",
3529
3529
  "builtinBadge": "intégré",
3530
+ "newHeading": "Nouveaux pipelines disponibles",
3531
+ "newDescription": "De nouveaux pipelines intégrés sont arrivés. Ajoutez-les à la bibliothèque de ce tableau.",
3532
+ "add": "Ajouter",
3530
3533
  "reseed": "Régénérer",
3531
3534
  "delete": "Supprimer",
3532
3535
  "updatesHeading": "Mises à jour disponibles",
@@ -3538,6 +3538,9 @@
3538
3538
  "invalidHeading": "צינורות לא תקפים",
3539
3539
  "invalidDescription": "אלה מפנים לסוכן חסר או מוגדרים בצורה שגויה, ולכן ייכשלו (או ירוצו בצורה שגויה) בהתחלה. מחק צינור מותאם אישית, או זרע מחדש צינור מובנה כדי לשחזר את הגדרת הקטלוג שלו.",
3540
3540
  "builtinBadge": "מובנה",
3541
+ "newHeading": "צינורות חדשים זמינים",
3542
+ "newDescription": "הגיעו צינורות מובנים חדשים. הוסף אותם לספריית הלוח הזה.",
3543
+ "add": "הוסף",
3541
3544
  "reseed": "זרע מחדש",
3542
3545
  "delete": "מחק",
3543
3546
  "updatesHeading": "עדכונים זמינים",
@@ -3260,6 +3260,9 @@
3260
3260
  "invalidHeading": "Pipeline non valide",
3261
3261
  "invalidDescription": "Queste fanno riferimento a un agente mancante o sono configurate male, quindi fallirebbero (o funzionerebbero male) all'avvio. Elimina una pipeline personalizzata, oppure ripristina una integrata per recuperare la sua definizione dal catalogo.",
3262
3262
  "builtinBadge": "integrata",
3263
+ "newHeading": "Nuove pipeline disponibili",
3264
+ "newDescription": "Sono state rilasciate nuove pipeline integrate. Aggiungile alla libreria di questa board.",
3265
+ "add": "Aggiungi",
3263
3266
  "reseed": "Ripristina",
3264
3267
  "delete": "Elimina",
3265
3268
  "updatesHeading": "Aggiornamenti disponibili",
@@ -3539,6 +3539,9 @@
3539
3539
  "invalidHeading": "無効なパイプライン",
3540
3540
  "invalidDescription": "存在しないエージェントを参照しているか設定が誤っているため、開始時に失敗(または誤動作)します。カスタムパイプラインを削除するか、ビルトインを再シードしてカタログ定義を復元してください。",
3541
3541
  "builtinBadge": "ビルトイン",
3542
+ "newHeading": "新しいパイプラインがあります",
3543
+ "newDescription": "新しい組み込みパイプラインが追加されました。このボードのライブラリに追加してください。",
3544
+ "add": "追加",
3542
3545
  "reseed": "再シード",
3543
3546
  "delete": "削除",
3544
3547
  "updatesHeading": "更新あり",
@@ -3527,6 +3527,9 @@
3527
3527
  "invalidHeading": "Nieprawidłowe pipeline'y",
3528
3528
  "invalidDescription": "Odwołują się do brakującego agenta lub są źle skonfigurowane, więc zawiodłyby (lub wykonałyby się błędnie) przy starcie. Usuń pipeline niestandardowy albo zregeneruj wbudowany, aby przywrócić jego definicję z katalogu.",
3529
3529
  "builtinBadge": "wbudowany",
3530
+ "newHeading": "Dostępne nowe pipeline'y",
3531
+ "newDescription": "Pojawiły się nowe wbudowane pipeline'y. Dodaj je do biblioteki tej tablicy.",
3532
+ "add": "Dodaj",
3530
3533
  "reseed": "Zregeneruj",
3531
3534
  "delete": "Usuń",
3532
3535
  "updatesHeading": "Dostępne aktualizacje",
@@ -3539,6 +3539,9 @@
3539
3539
  "invalidHeading": "Geçersiz pipeline'lar",
3540
3540
  "invalidDescription": "Bunlar eksik bir agent'a referans veriyor veya yanlış yapılandırılmış, bu yüzden başlangıçta başarısız olur (veya hatalı çalışır). Özel bir pipeline'ı silin ya da katalog tanımını geri yüklemek için bir yerleşik pipeline'ı yeniden tohumlayın.",
3541
3541
  "builtinBadge": "yerleşik",
3542
+ "newHeading": "Yeni pipeline'lar mevcut",
3543
+ "newDescription": "Yeni yerleşik pipeline'lar geldi. Bu panonun kütüphanesine ekleyin.",
3544
+ "add": "Ekle",
3542
3545
  "reseed": "Yeniden tohumla",
3543
3546
  "delete": "Sil",
3544
3547
  "updatesHeading": "Güncellemeler mevcut",
@@ -3527,6 +3527,9 @@
3527
3527
  "invalidHeading": "Недійсні пайплайни",
3528
3528
  "invalidDescription": "Вони посилаються на відсутнього агента або неправильно налаштовані, тож зазнали б помилки (або виконалися б неправильно) під час старту. Видаліть власний пайплайн або перегенеруйте вбудований, щоб відновити його визначення з каталогу.",
3529
3529
  "builtinBadge": "вбудований",
3530
+ "newHeading": "Доступні нові пайплайни",
3531
+ "newDescription": "З'явилися нові вбудовані пайплайни. Додайте їх до бібліотеки цієї дошки.",
3532
+ "add": "Додати",
3530
3533
  "reseed": "Перегенерувати",
3531
3534
  "delete": "Видалити",
3532
3535
  "updatesHeading": "Доступні оновлення",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.141.2",
3
+ "version": "0.142.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",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.150.0"
43
+ "@cat-factory/contracts": "0.151.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",