@cat-factory/app 0.96.5 → 0.97.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.
@@ -12,6 +12,7 @@ import type {
12
12
  } from '~/types/domain'
13
13
  import FrontendBindingsResolved from '~/components/panels/inspector/FrontendBindingsResolved.vue'
14
14
  import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
15
+ import { apiErrorEnvelope } from '~/composables/api/errors'
15
16
 
16
17
  // Frontend-frame (`type: 'frontend'`) configuration: how to build, serve, and mock this
17
18
  // frontend for a self-contained UI test (+ an optional browsable preview on local/node),
@@ -119,7 +120,9 @@ const showPreview = ref(false)
119
120
  // frame links to its repo the same way a service does (`github_repos.block_id`).
120
121
  const repoLink = computed(() => github.repoForBlock(props.block.id))
121
122
  const detecting = ref(false)
122
- const detectError = ref(false)
123
+ // The detect failure message to show, or null when there's no error. Holds the SERVER's real
124
+ // message (the backend raises an actionable one for an unreadable repo) instead of a fixed line.
125
+ const detectError = ref<string | null>(null)
123
126
  const detectResult = ref<FrontendConfigRecommendation | null>(null)
124
127
 
125
128
  // A detection result is scoped to the inspected block — clear it (and any error) when the
@@ -128,18 +131,20 @@ watch(
128
131
  () => props.block.id,
129
132
  () => {
130
133
  detectResult.value = null
131
- detectError.value = false
134
+ detectError.value = null
132
135
  },
133
136
  )
134
137
 
135
138
  async function detectFromRepo() {
136
139
  const repo = repoLink.value
137
140
  if (!repo) {
138
- detectError.value = true
141
+ // The frame points at a repo that isn't in the connected-repo projection, so we can't resolve
142
+ // its owner/name to ask the backend. A "sync/connect GitHub" problem, not a read failure.
143
+ detectError.value = t('inspector.detectRepoUnresolved')
139
144
  return
140
145
  }
141
146
  detecting.value = true
142
- detectError.value = false
147
+ detectError.value = null
143
148
  detectResult.value = null
144
149
  try {
145
150
  const result = await infra.detectFrontendConfig({
@@ -151,8 +156,13 @@ async function detectFromRepo() {
151
156
  // When nothing was detected the "none" hint tells the user to set the frontend directory —
152
157
  // that field lives in the (collapsed) Build group, so open it so the advice is actionable.
153
158
  if (!result.detected) showBuild.value = true
154
- } catch {
155
- detectError.value = true
159
+ } catch (e) {
160
+ // Surface the server's real message (an actionable "couldn't read the repo — check App access"
161
+ // for a read fault), falling back to the generic line only when none is available.
162
+ detectError.value =
163
+ apiErrorEnvelope(e)?.message ??
164
+ (e instanceof Error ? e.message : null) ??
165
+ t('inspector.frontendConfig.detect.error')
156
166
  } finally {
157
167
  detecting.value = false
158
168
  }
@@ -243,7 +253,7 @@ onUnmounted(() => preview.stopPolling(props.block.id))
243
253
  </p>
244
254
 
245
255
  <p v-if="detectError" class="text-[11px] text-rose-300/80">
246
- {{ t('inspector.frontendConfig.detect.error') }}
256
+ {{ detectError }}
247
257
  </p>
248
258
 
249
259
  <template v-if="detectResult && !detecting">
@@ -18,6 +18,7 @@ import type {
18
18
  } from '@cat-factory/contracts'
19
19
  import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
20
20
  import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
21
+ import { apiErrorEnvelope } from '~/composables/api/errors'
21
22
 
22
23
  // Service-level (frame) configuration: the service-owned PROVISIONING — the provision
23
24
  // TYPE this service produces (`infraless` / `docker-compose` / `kubernetes` / `custom`)
@@ -284,7 +285,10 @@ function applyPicked() {
284
285
  // every field stays editable, and the engine-level URL/namespace suggestions are surfaced
285
286
  // read-only (the workspace handler owns them). Nothing is persisted server-side by detection.
286
287
  const detecting = ref(false)
287
- const detectError = ref(false)
288
+ // The detect failure message to show, or null when there's no error. Holds the SERVER's real
289
+ // message (the backend now raises an actionable one for an unreadable repo) so the user sees why
290
+ // detection failed instead of a fixed, vague line.
291
+ const detectError = ref<string | null>(null)
288
292
  const detectResult = ref<ProvisioningRecommendation | null>(null)
289
293
  // Advisory, LOCAL-ONLY selection: which compose `services:` key the user picked. It is NOT persisted
290
294
  // (the compose backend targets the file, not a single service), so it lives only in component state
@@ -298,7 +302,7 @@ watch(
298
302
  () => props.block.id,
299
303
  () => {
300
304
  detectResult.value = null
301
- detectError.value = false
305
+ detectError.value = null
302
306
  pickedComposeService.value = null
303
307
  },
304
308
  )
@@ -308,11 +312,14 @@ async function detectFromRepo() {
308
312
  if (!ctx) return
309
313
  const repo = github.repoFor(ctx.githubId)
310
314
  if (!repo) {
311
- detectError.value = true
315
+ // The frame points at a repo that isn't in the connected-repo projection, so we can't resolve
316
+ // its owner/name to ask the backend. That's a "sync/connect GitHub" problem, NOT "couldn't read
317
+ // the repo" — say so specifically.
318
+ detectError.value = t('inspector.detectRepoUnresolved')
312
319
  return
313
320
  }
314
321
  detecting.value = true
315
- detectError.value = false
322
+ detectError.value = null
316
323
  try {
317
324
  const rec = await infra.detectProvisioning({
318
325
  owner: repo.owner,
@@ -342,8 +349,13 @@ async function detectFromRepo() {
342
349
  board.updateBlock(props.block.id, { provisioning: rec.provisioning })
343
350
  if (rec.provisioning.type === 'kubernetes') seedKubeSource(rec.provisioning.manifestSource)
344
351
  }
345
- } catch {
346
- detectError.value = true
352
+ } catch (e) {
353
+ // Surface the server's real message (an actionable "couldn't read the repo — check App access"
354
+ // for a read fault), falling back to the generic line only when none is available.
355
+ detectError.value =
356
+ apiErrorEnvelope(e)?.message ??
357
+ (e instanceof Error ? e.message : null) ??
358
+ t('inspector.testConfig.detect.error')
347
359
  } finally {
348
360
  detecting.value = false
349
361
  }
@@ -452,7 +464,7 @@ function setSize(value: InstanceSize) {
452
464
  </p>
453
465
 
454
466
  <p v-if="detectError" class="text-[11px] text-rose-300/80">
455
- {{ t('inspector.testConfig.detect.error') }}
467
+ {{ detectError }}
456
468
  </p>
457
469
 
458
470
  <template v-if="detectResult && !detecting">
@@ -48,6 +48,7 @@ const CONFLICT_TITLE_KEYS: Record<
48
48
  provision_type_unhandled: 'errors.conflict.title.provision_type_unhandled',
49
49
  preset_unsatisfiable: 'errors.conflict.title.preset_unsatisfiable',
50
50
  visual_pipeline_no_frontend: 'errors.conflict.title.visual_pipeline_no_frontend',
51
+ deployer_required_before_tester: 'errors.conflict.title.deployer_required_before_tester',
51
52
  }
52
53
 
53
54
  /**
@@ -346,6 +346,18 @@ export const SYSTEM_AGENT_META: Record<string, AgentArchetype> = {
346
346
  color: '#22d3ee',
347
347
  description: 'Maps the repository into the service → modules blueprint.',
348
348
  },
349
+ // The single environment provisioner: an operational (non-LLM) step that stands up the ephemeral
350
+ // environment the tester / human-test gate run against for a kubernetes/custom service, and is a
351
+ // fast no-op for docker-compose / infraless. Seeded before the first tester/human-test step in the
352
+ // built-in pipelines, so it needs display metadata (else it renders as a generic gray "Agent").
353
+ deployer: {
354
+ kind: 'deployer',
355
+ label: 'Deployer',
356
+ icon: 'i-lucide-cloud-upload',
357
+ color: '#34d399',
358
+ description:
359
+ 'Provisions the ephemeral environment the tester and human-test gate run against (kubernetes / custom services); a no-op for docker-compose / infraless.',
360
+ },
349
361
  // The Initiative Planning pipeline's two steps. Only runnable on an initiative
350
362
  // block (pl_initiative — enforced by the engine), so they are display-metadata
351
363
  // system kinds, never palette archetypes.
@@ -428,7 +428,8 @@
428
428
  "bootstrap_reference_missing": "Reference architecture is gone",
429
429
  "provision_type_unhandled": "No handler for this provision type",
430
430
  "preset_unsatisfiable": "Model preset can't run this pipeline",
431
- "visual_pipeline_no_frontend": "No frontend to test"
431
+ "visual_pipeline_no_frontend": "No frontend to test",
432
+ "deployer_required_before_tester": "Add a Deployer before the Tester"
432
433
  },
433
434
  "fallbackMessage": "This action conflicts with the current state.",
434
435
  "providersUnconfigured": {
@@ -503,6 +504,7 @@
503
504
  }
504
505
  },
505
506
  "inspector": {
507
+ "detectRepoUnresolved": "This block's repository isn't among the connected GitHub repos, so it can't be read. Connect or sync GitHub for it, then retry.",
506
508
  "container": {
507
509
  "title": "Contents",
508
510
  "hint": "The modules and tasks inside this block. Click an entry to select it on the board.",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "La inicialización no se puede reintentar",
392
392
  "bootstrap_reference_missing": "La arquitectura de referencia ha desaparecido",
393
393
  "preset_unsatisfiable": "El preajuste de modelo no puede ejecutar esta canalización",
394
- "visual_pipeline_no_frontend": "No hay frontend que probar"
394
+ "visual_pipeline_no_frontend": "No hay frontend que probar",
395
+ "deployer_required_before_tester": "Añade un Deployer antes del Tester"
395
396
  },
396
397
  "fallbackMessage": "Esta acción entra en conflicto con el estado actual.",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "El repositorio de este bloque no está entre los repositorios de GitHub conectados, por lo que no se puede leer. Conecta o sincroniza GitHub para él y vuelve a intentarlo.",
463
465
  "container": {
464
466
  "title": "Contenido",
465
467
  "hint": "Los módulos y tareas dentro de este bloque. Haz clic en una entrada para seleccionarla en el tablero.",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "L’initialisation ne peut pas être relancée",
392
392
  "bootstrap_reference_missing": "L’architecture de référence a disparu",
393
393
  "preset_unsatisfiable": "Le préréglage de modèle ne peut pas exécuter ce pipeline",
394
- "visual_pipeline_no_frontend": "Aucun frontend à tester"
394
+ "visual_pipeline_no_frontend": "Aucun frontend à tester",
395
+ "deployer_required_before_tester": "Ajoutez un Deployer avant le Testeur"
395
396
  },
396
397
  "fallbackMessage": "Cette action est en conflit avec l’état actuel.",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "Le dépôt de ce bloc ne figure pas parmi les dépôts GitHub connectés et ne peut donc pas être lu. Connectez ou synchronisez GitHub pour ce dépôt, puis réessayez.",
463
465
  "container": {
464
466
  "title": "Contenu",
465
467
  "hint": "Les modules et les tâches contenus dans ce bloc. Cliquez sur une entrée pour la sélectionner sur le tableau.",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "לא ניתן להריץ מחדש את האתחול",
392
392
  "bootstrap_reference_missing": "ארכיטקטורת ההפניה נעלמה",
393
393
  "preset_unsatisfiable": "קדם‑הגדרת המודל אינה יכולה להריץ צנרת זו",
394
- "visual_pipeline_no_frontend": "אין frontend לבדיקה"
394
+ "visual_pipeline_no_frontend": "אין frontend לבדיקה",
395
+ "deployer_required_before_tester": "הוסף Deployer לפני ה-Tester"
395
396
  },
396
397
  "fallbackMessage": "פעולה זו מתנגשת עם המצב הנוכחי.",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "מאגר הקוד של בלוק זה אינו נמצא בין מאגרי GitHub המחוברים, ולכן לא ניתן לקרוא אותו. חבר או סנכרן את GitHub עבורו ונסה שוב.",
463
465
  "container": {
464
466
  "title": "תוכן",
465
467
  "hint": "המודולים והמשימות בתוך בלוק זה. לחץ על פריט כדי לבחור אותו בלוח.",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "ブートストラップは再試行できません",
392
392
  "bootstrap_reference_missing": "リファレンスアーキテクチャが見つかりません",
393
393
  "preset_unsatisfiable": "モデルプリセットではこのパイプラインを実行できません",
394
- "visual_pipeline_no_frontend": "テスト対象のフロントエンドがありません"
394
+ "visual_pipeline_no_frontend": "テスト対象のフロントエンドがありません",
395
+ "deployer_required_before_tester": "テスターの前にDeployerを追加してください"
395
396
  },
396
397
  "fallbackMessage": "この操作は現在の状態と競合します。",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "このブロックのリポジトリは接続済みの GitHub リポジトリに含まれていないため、読み取れません。GitHub を接続または同期してから、もう一度お試しください。",
463
465
  "container": {
464
466
  "title": "内容",
465
467
  "hint": "このブロック内のモジュールとタスク。エントリをクリックするとボード上で選択されます。",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "Inicjalizacji nie można ponowić",
392
392
  "bootstrap_reference_missing": "Architektura referencyjna zniknęła",
393
393
  "preset_unsatisfiable": "Ten zestaw modeli nie może uruchomić tego potoku",
394
- "visual_pipeline_no_frontend": "Brak frontendu do przetestowania"
394
+ "visual_pipeline_no_frontend": "Brak frontendu do przetestowania",
395
+ "deployer_required_before_tester": "Dodaj Deployer przed Testerem"
395
396
  },
396
397
  "fallbackMessage": "Ta akcja jest sprzeczna z bieżącym stanem.",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "Repozytorium tego bloku nie znajduje się wśród połączonych repozytoriów GitHub, więc nie można go odczytać. Połącz lub zsynchronizuj GitHub dla tego repozytorium, a następnie spróbuj ponownie.",
463
465
  "container": {
464
466
  "title": "Zawartość",
465
467
  "hint": "Moduły i zadania wewnątrz tego bloku. Kliknij pozycję, aby zaznaczyć ją na tablicy.",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "Bootstrap yeniden denenemiyor",
392
392
  "bootstrap_reference_missing": "Referans mimari kayıp",
393
393
  "preset_unsatisfiable": "Model ön ayarı bu ardışık düzeni çalıştıramıyor",
394
- "visual_pipeline_no_frontend": "Test edilecek bir frontend yok"
394
+ "visual_pipeline_no_frontend": "Test edilecek bir frontend yok",
395
+ "deployer_required_before_tester": "Tester’dan önce bir Deployer ekleyin"
395
396
  },
396
397
  "fallbackMessage": "Bu eylem mevcut durumla çelişiyor.",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "Bu bloğun deposu bağlı GitHub depoları arasında değil, bu yüzden okunamıyor. Bunun için GitHub'ı bağlayın veya eşitleyin, ardından yeniden deneyin.",
463
465
  "container": {
464
466
  "title": "İçerik",
465
467
  "hint": "Bu bloğun içindeki modüller ve görevler. Bir öğeye tıklayarak panoda seçin.",
@@ -391,7 +391,8 @@
391
391
  "bootstrap_not_retryable": "Ініціалізацію неможливо повторити",
392
392
  "bootstrap_reference_missing": "Еталонна архітектура зникла",
393
393
  "preset_unsatisfiable": "Пресет моделі не може запустити цей конвеєр",
394
- "visual_pipeline_no_frontend": "Немає фронтенду для тестування"
394
+ "visual_pipeline_no_frontend": "Немає фронтенду для тестування",
395
+ "deployer_required_before_tester": "Додайте Deployer перед Tester"
395
396
  },
396
397
  "fallbackMessage": "Ця дія суперечить поточному стану.",
397
398
  "providersUnconfigured": {
@@ -460,6 +461,7 @@
460
461
  }
461
462
  },
462
463
  "inspector": {
464
+ "detectRepoUnresolved": "Репозиторій цього блоку відсутній серед підключених репозиторіїв GitHub, тому його не вдається прочитати. Підключіть або синхронізуйте GitHub для нього та повторіть спробу.",
463
465
  "container": {
464
466
  "title": "Вміст",
465
467
  "hint": "Модулі та завдання всередині цього блока. Клацніть запис, щоб вибрати його на дошці.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.96.5",
3
+ "version": "0.97.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",
@@ -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.107.0"
37
+ "@cat-factory/contracts": "0.108.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",