@cat-factory/app 0.69.1 → 0.70.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.
@@ -18,6 +18,9 @@ const segments = ref<Seg[]>([])
18
18
  // Epic→member membership links (distinct style from dependency edges).
19
19
  type MemberSeg = { id: string; x1: number; y1: number; x2: number; y2: number }
20
20
  const memberSegments = ref<MemberSeg[]>([])
21
+ // Frontend frame → bound service frame links (from a frontend's backend bindings).
22
+ type FrontendSeg = { id: string; x1: number; y1: number; x2: number; y2: number }
23
+ const frontendSegments = ref<FrontendSeg[]>([])
21
24
 
22
25
  // task → its dependencies, both ends being tasks
23
26
  const taskDeps = computed(() => {
@@ -43,6 +46,26 @@ const epicLinks = computed(() => {
43
46
  return out
44
47
  })
45
48
 
49
+ // frontend frame → each service it binds a backend upstream to (its ephemeral env is the
50
+ // "service under test"). The link IS a `service`-sourced backend binding on the frontend's
51
+ // config; deduped so multiple env vars bound to the same service draw one edge.
52
+ const frontendLinks = computed(() => {
53
+ const out: { id: string; source: string; target: string }[] = []
54
+ for (const f of board.frames) {
55
+ if (f.type !== 'frontend') continue
56
+ const seen = new Set<string>()
57
+ for (const binding of f.frontendConfig?.backendBindings ?? []) {
58
+ if (binding.source.kind !== 'service') continue
59
+ const serviceId = binding.source.serviceBlockId
60
+ if (seen.has(serviceId)) continue
61
+ seen.add(serviceId)
62
+ if (board.getBlock(serviceId))
63
+ out.push({ id: `${f.id}__fe__${serviceId}`, source: f.id, target: serviceId })
64
+ }
65
+ }
66
+ return out
67
+ })
68
+
46
69
  /** Resolve a task's anchor: walk up task → module → service to the first card
47
70
  * that's actually rendered (a container may be collapsed). */
48
71
  function anchorEl(taskId: string): HTMLElement | null {
@@ -112,6 +135,23 @@ function recompute() {
112
135
  members.push({ id: link.id, x1: start.x, y1: start.y, x2: end.x, y2: end.y })
113
136
  }
114
137
  memberSegments.value = members
138
+
139
+ const fes: FrontendSeg[] = []
140
+ for (const link of frontendLinks.value) {
141
+ const a = anchorEl(link.source)
142
+ const b = anchorEl(link.target)
143
+ if (!a || !b || a === b) continue
144
+ const ra = a.getBoundingClientRect()
145
+ const rb = b.getBoundingClientRect()
146
+ const ax = ra.left + ra.width / 2 - origin.left
147
+ const ay = ra.top + ra.height / 2 - origin.top
148
+ const bx = rb.left + rb.width / 2 - origin.left
149
+ const by = rb.top + rb.height / 2 - origin.top
150
+ const start = border(ax, ay, ra.width / 2, ra.height / 2, bx, by)
151
+ const end = border(bx, by, rb.width / 2, rb.height / 2, ax, ay)
152
+ fes.push({ id: link.id, x1: start.x, y1: start.y, x2: end.x, y2: end.y })
153
+ }
154
+ frontendSegments.value = fes
115
155
  }
116
156
 
117
157
  const { pause, resume } = useRafFn(recompute, { immediate: false })
@@ -144,8 +184,34 @@ onBeforeUnmount(pause)
144
184
  >
145
185
  <path d="M0,0 L10,5 L0,10 z" fill="#64748b" />
146
186
  </marker>
187
+ <marker
188
+ id="frontend-arrow"
189
+ viewBox="0 0 10 10"
190
+ refX="8"
191
+ refY="5"
192
+ markerWidth="6"
193
+ markerHeight="6"
194
+ orient="auto-start-reverse"
195
+ >
196
+ <path d="M0,0 L10,5 L0,10 z" fill="#22d3ee" />
197
+ </marker>
147
198
  </defs>
148
199
 
200
+ <!-- frontend frame → bound service frame links (cyan, arrow toward the service under test) -->
201
+ <line
202
+ v-for="s in frontendSegments"
203
+ :key="s.id"
204
+ :x1="s.x1"
205
+ :y1="s.y1"
206
+ :x2="s.x2"
207
+ :y2="s.y2"
208
+ stroke="#22d3ee"
209
+ :stroke-width="1.5"
210
+ stroke-dasharray="1 4"
211
+ :stroke-opacity="0.6"
212
+ marker-end="url(#frontend-arrow)"
213
+ />
214
+
149
215
  <!-- epic → member membership links (soft violet, no arrowhead) -->
150
216
  <line
151
217
  v-for="s in memberSegments"
@@ -7,6 +7,7 @@ import TaskAgentConfig from '~/components/panels/inspector/TaskAgentConfig.vue'
7
7
  import ServiceTestConfig from '~/components/panels/inspector/ServiceTestConfig.vue'
8
8
  import ServiceFragments from '~/components/panels/inspector/ServiceFragments.vue'
9
9
  import ServiceReleaseHealthConfig from '~/components/panels/inspector/ServiceReleaseHealthConfig.vue'
10
+ import FrontendConfig from '~/components/panels/inspector/FrontendConfig.vue'
10
11
  import ContainerSummary from '~/components/panels/inspector/ContainerSummary.vue'
11
12
  import TaskDependencies from '~/components/panels/inspector/TaskDependencies.vue'
12
13
  import TaskStructure from '~/components/panels/inspector/TaskStructure.vue'
@@ -435,6 +436,9 @@ const showOriginalDescription = ref(false)
435
436
 
436
437
  <!-- service / module: tasks summary -->
437
438
  <ContainerSummary v-if="isContainer" :block="block" />
439
+ <!-- frontend (frame): build/serve/mock config + backend bindings (board links) -->
440
+ <FrontendConfig v-if="isFrame && block.type === 'frontend'" :block="block" />
441
+
438
442
  <!-- service (frame): test infra + provisioning configuration -->
439
443
  <ServiceTestConfig v-if="isFrame" :block="block" />
440
444
 
@@ -0,0 +1,349 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type {
4
+ Block,
5
+ FrontendBackendBinding,
6
+ FrontendConfig,
7
+ FrontendEnvInjection,
8
+ FrontendPackageManager,
9
+ FrontendServeMode,
10
+ } from '~/types/domain'
11
+
12
+ // Frontend-frame (`type: 'frontend'`) configuration: how to build, serve, and mock this
13
+ // frontend for a self-contained UI test (+ an optional browsable preview on local/node),
14
+ // and its backend bindings. Each binding names an env var the frontend reads for an upstream
15
+ // URL and where that URL resolves — a bound SERVICE frame's ephemeral env (the service under
16
+ // test), or WireMock. The bindings ARE the board's frontend→service links. Persisted as a
17
+ // serialized FrontendConfig on the block via the shared updateBlock PATCH.
18
+ const props = defineProps<{ block: Block }>()
19
+
20
+ const board = useBoardStore()
21
+ const { t } = useI18n()
22
+
23
+ const config = computed<FrontendConfig>(() => props.block.frontendConfig ?? { backendBindings: [] })
24
+ const bindings = computed(() => config.value.backendBindings ?? [])
25
+
26
+ // Merge a partial onto the current config, preserving the other fields, and persist. A field
27
+ // set to undefined is dropped (JSON.stringify omits it), so the harness default applies.
28
+ function save(patch: Partial<FrontendConfig>) {
29
+ const base: FrontendConfig = props.block.frontendConfig ?? { backendBindings: [] }
30
+ board.updateBlock(props.block.id, { frontendConfig: { ...base, ...patch } })
31
+ }
32
+
33
+ // A trimmed string field: an empty value clears it (undefined) so the harness default applies.
34
+ function saveText(field: keyof FrontendConfig, value: string) {
35
+ save({ [field]: value.trim() || undefined } as Partial<FrontendConfig>)
36
+ }
37
+
38
+ // The serve port must be an integer in [1, 65535] (the contract's schema bounds). Coerce and
39
+ // clamp to a valid port, dropping anything else to undefined (clears it → the harness default),
40
+ // so an out-of-range or non-integer value never 422s the PATCH.
41
+ function saveServePort(value: string) {
42
+ const n = Math.trunc(Number(value.trim()))
43
+ save({ servePort: Number.isInteger(n) && n >= 1 && n <= 65535 ? n : undefined })
44
+ }
45
+
46
+ const PACKAGE_MANAGERS: FrontendPackageManager[] = ['pnpm', 'npm', 'yarn']
47
+ const packageManager = computed(() => config.value.packageManager ?? 'pnpm')
48
+ const serveMode = computed<FrontendServeMode>(() => config.value.serveMode ?? 'static')
49
+ const envInjection = computed<FrontendEnvInjection>(() => config.value.envInjection ?? 'build')
50
+
51
+ // The service frames on the board a binding can point at (its ephemeral env URL becomes the
52
+ // service under test). Every other binding resolves to WireMock.
53
+ const serviceFrames = computed(() => board.frames.filter((b) => b.type === 'service'))
54
+
55
+ // USelect options for a binding's source: WireMock, or one of the service frames.
56
+ const sourceItems = computed(() => [
57
+ { label: t('inspector.frontendConfig.bindings.mock'), value: 'mock' },
58
+ ...serviceFrames.value.map((f) => ({ label: f.title || f.id, value: f.id })),
59
+ ])
60
+
61
+ // The select value for a binding: 'mock', or the bound service block id.
62
+ function sourceValue(binding: FrontendBackendBinding): string {
63
+ return binding.source.kind === 'service' ? binding.source.serviceBlockId : 'mock'
64
+ }
65
+
66
+ function replaceBinding(index: number, next: FrontendBackendBinding) {
67
+ save({ backendBindings: bindings.value.map((b, i) => (i === index ? next : b)) })
68
+ }
69
+
70
+ function setBindingEnvVar(index: number, value: string) {
71
+ const b = bindings.value[index]
72
+ if (b) replaceBinding(index, { ...b, envVar: value.trim() })
73
+ }
74
+
75
+ function setBindingSource(index: number, value: string) {
76
+ const b = bindings.value[index]
77
+ if (!b) return
78
+ const source: FrontendBackendBinding['source'] =
79
+ value === 'mock' ? { kind: 'mock' } : { kind: 'service', serviceBlockId: value }
80
+ replaceBinding(index, { ...b, source })
81
+ }
82
+
83
+ function addBinding() {
84
+ save({ backendBindings: [...bindings.value, { envVar: '', source: { kind: 'mock' } }] })
85
+ }
86
+
87
+ function removeBinding(index: number) {
88
+ save({ backendBindings: bindings.value.filter((_, i) => i !== index) })
89
+ }
90
+ </script>
91
+
92
+ <template>
93
+ <div class="space-y-3">
94
+ <div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
95
+ {{ t('inspector.frontendConfig.title') }}
96
+ </div>
97
+ <p class="text-[11px] leading-snug text-slate-500">
98
+ {{ t('inspector.frontendConfig.hint') }}
99
+ </p>
100
+
101
+ <!-- Package manager -->
102
+ <div class="space-y-1">
103
+ <span class="text-[11px] text-slate-400">{{
104
+ t('inspector.frontendConfig.packageManager')
105
+ }}</span>
106
+ <div class="flex flex-wrap gap-1">
107
+ <UButton
108
+ v-for="pm in PACKAGE_MANAGERS"
109
+ :key="pm"
110
+ :color="packageManager === pm ? 'primary' : 'neutral'"
111
+ :variant="packageManager === pm ? 'soft' : 'ghost'"
112
+ size="xs"
113
+ @click="save({ packageManager: pm })"
114
+ >
115
+ {{ pm }}
116
+ </UButton>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- Build: install command + build script + output dir -->
121
+ <div class="space-y-1">
122
+ <label class="text-[11px] text-slate-400">{{
123
+ t('inspector.frontendConfig.installCommand')
124
+ }}</label>
125
+ <UInput
126
+ :model-value="config.installCommand ?? ''"
127
+ size="xs"
128
+ class="font-mono"
129
+ maxlength="400"
130
+ placeholder="pnpm install --frozen-lockfile"
131
+ @blur="(e: FocusEvent) => saveText('installCommand', (e.target as HTMLInputElement).value)"
132
+ @keydown.enter="
133
+ (e: KeyboardEvent) => saveText('installCommand', (e.target as HTMLInputElement).value)
134
+ "
135
+ />
136
+ </div>
137
+
138
+ <div class="grid grid-cols-2 gap-2">
139
+ <div class="space-y-1">
140
+ <label class="text-[11px] text-slate-400">{{
141
+ t('inspector.frontendConfig.buildScript')
142
+ }}</label>
143
+ <UInput
144
+ :model-value="config.buildScript ?? ''"
145
+ size="xs"
146
+ class="font-mono"
147
+ maxlength="200"
148
+ placeholder="build"
149
+ @blur="(e: FocusEvent) => saveText('buildScript', (e.target as HTMLInputElement).value)"
150
+ @keydown.enter="
151
+ (e: KeyboardEvent) => saveText('buildScript', (e.target as HTMLInputElement).value)
152
+ "
153
+ />
154
+ </div>
155
+ <div class="space-y-1">
156
+ <label class="text-[11px] text-slate-400">{{
157
+ t('inspector.frontendConfig.outputDir')
158
+ }}</label>
159
+ <UInput
160
+ :model-value="config.outputDir ?? ''"
161
+ size="xs"
162
+ class="font-mono"
163
+ maxlength="400"
164
+ placeholder="dist"
165
+ @blur="(e: FocusEvent) => saveText('outputDir', (e.target as HTMLInputElement).value)"
166
+ @keydown.enter="
167
+ (e: KeyboardEvent) => saveText('outputDir', (e.target as HTMLInputElement).value)
168
+ "
169
+ />
170
+ </div>
171
+ </div>
172
+
173
+ <!-- Serve: mode (static vs command) + serve script (command mode) + port -->
174
+ <div class="space-y-1">
175
+ <span class="text-[11px] text-slate-400">{{ t('inspector.frontendConfig.serveMode') }}</span>
176
+ <div class="flex flex-wrap gap-1">
177
+ <UButton
178
+ :color="serveMode === 'static' ? 'primary' : 'neutral'"
179
+ :variant="serveMode === 'static' ? 'soft' : 'ghost'"
180
+ size="xs"
181
+ @click="save({ serveMode: 'static' })"
182
+ >
183
+ {{ t('inspector.frontendConfig.serveStatic') }}
184
+ </UButton>
185
+ <UButton
186
+ :color="serveMode === 'command' ? 'primary' : 'neutral'"
187
+ :variant="serveMode === 'command' ? 'soft' : 'ghost'"
188
+ size="xs"
189
+ @click="save({ serveMode: 'command' })"
190
+ >
191
+ {{ t('inspector.frontendConfig.serveCommand') }}
192
+ </UButton>
193
+ </div>
194
+ <p class="text-[11px] leading-snug text-slate-500">
195
+ {{ t('inspector.frontendConfig.serveModeHint') }}
196
+ </p>
197
+ </div>
198
+
199
+ <div v-if="serveMode === 'command'" class="space-y-1">
200
+ <label class="text-[11px] text-slate-400">{{
201
+ t('inspector.frontendConfig.serveScript')
202
+ }}</label>
203
+ <UInput
204
+ :model-value="config.serveScript ?? ''"
205
+ size="xs"
206
+ class="font-mono"
207
+ maxlength="200"
208
+ placeholder="preview"
209
+ @blur="(e: FocusEvent) => saveText('serveScript', (e.target as HTMLInputElement).value)"
210
+ @keydown.enter="
211
+ (e: KeyboardEvent) => saveText('serveScript', (e.target as HTMLInputElement).value)
212
+ "
213
+ />
214
+ </div>
215
+
216
+ <div class="grid grid-cols-2 gap-2">
217
+ <div class="space-y-1">
218
+ <label class="text-[11px] text-slate-400">{{
219
+ t('inspector.frontendConfig.servePort')
220
+ }}</label>
221
+ <UInput
222
+ :model-value="config.servePort != null ? String(config.servePort) : ''"
223
+ type="number"
224
+ min="1"
225
+ max="65535"
226
+ step="1"
227
+ size="xs"
228
+ class="font-mono"
229
+ placeholder="8080"
230
+ @blur="(e: FocusEvent) => saveServePort((e.target as HTMLInputElement).value)"
231
+ />
232
+ </div>
233
+ <div class="space-y-1">
234
+ <label class="text-[11px] text-slate-400">{{
235
+ t('inspector.frontendConfig.mockMappingsPath')
236
+ }}</label>
237
+ <UInput
238
+ :model-value="config.mockMappingsPath ?? ''"
239
+ size="xs"
240
+ class="font-mono"
241
+ maxlength="400"
242
+ placeholder="mocks/"
243
+ @blur="
244
+ (e: FocusEvent) => saveText('mockMappingsPath', (e.target as HTMLInputElement).value)
245
+ "
246
+ @keydown.enter="
247
+ (e: KeyboardEvent) => saveText('mockMappingsPath', (e.target as HTMLInputElement).value)
248
+ "
249
+ />
250
+ </div>
251
+ </div>
252
+
253
+ <!-- Env injection: build-time env vars vs a runtime window.env shim -->
254
+ <div class="space-y-1">
255
+ <span class="text-[11px] text-slate-400">{{
256
+ t('inspector.frontendConfig.envInjection')
257
+ }}</span>
258
+ <div class="flex flex-wrap gap-1">
259
+ <UButton
260
+ :color="envInjection === 'build' ? 'primary' : 'neutral'"
261
+ :variant="envInjection === 'build' ? 'soft' : 'ghost'"
262
+ size="xs"
263
+ @click="save({ envInjection: 'build' })"
264
+ >
265
+ {{ t('inspector.frontendConfig.envBuild') }}
266
+ </UButton>
267
+ <UButton
268
+ :color="envInjection === 'runtime' ? 'primary' : 'neutral'"
269
+ :variant="envInjection === 'runtime' ? 'soft' : 'ghost'"
270
+ size="xs"
271
+ @click="save({ envInjection: 'runtime' })"
272
+ >
273
+ {{ t('inspector.frontendConfig.envRuntime') }}
274
+ </UButton>
275
+ </div>
276
+ <p class="text-[11px] leading-snug text-slate-500">
277
+ {{ t('inspector.frontendConfig.envInjectionHint') }}
278
+ </p>
279
+ </div>
280
+
281
+ <!-- Backend bindings: env var → upstream. These double as the board's frontend→service links. -->
282
+ <div class="space-y-2 border-t border-slate-800 pt-2">
283
+ <div class="flex items-center justify-between">
284
+ <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
285
+ {{ t('inspector.frontendConfig.bindings.title') }}
286
+ </span>
287
+ <UButton
288
+ size="xs"
289
+ variant="ghost"
290
+ color="neutral"
291
+ icon="i-lucide-plus"
292
+ @click="addBinding"
293
+ />
294
+ </div>
295
+ <p class="text-[11px] leading-snug text-slate-500">
296
+ {{ t('inspector.frontendConfig.bindings.hint') }}
297
+ </p>
298
+
299
+ <div v-if="bindings.length" class="space-y-1.5">
300
+ <div v-for="(b, i) in bindings" :key="i" class="flex items-center gap-1">
301
+ <UInput
302
+ :model-value="b.envVar"
303
+ size="xs"
304
+ class="flex-1 font-mono"
305
+ maxlength="200"
306
+ placeholder="PUB_BACKEND_URL"
307
+ @blur="(e: FocusEvent) => setBindingEnvVar(i, (e.target as HTMLInputElement).value)"
308
+ @keydown.enter="
309
+ (e: KeyboardEvent) => setBindingEnvVar(i, (e.target as HTMLInputElement).value)
310
+ "
311
+ />
312
+ <USelect
313
+ :model-value="sourceValue(b)"
314
+ :items="sourceItems"
315
+ size="xs"
316
+ class="flex-1"
317
+ @update:model-value="(v: string) => setBindingSource(i, v)"
318
+ />
319
+ <UButton
320
+ size="xs"
321
+ variant="ghost"
322
+ color="neutral"
323
+ icon="i-lucide-x"
324
+ :title="t('inspector.frontendConfig.bindings.remove')"
325
+ @click="removeBinding(i)"
326
+ />
327
+ </div>
328
+ </div>
329
+ <div v-else class="text-[11px] text-slate-500">
330
+ {{ t('inspector.frontendConfig.bindings.empty') }}
331
+ </div>
332
+ </div>
333
+
334
+ <!-- Browsable preview (local/node only). -->
335
+ <div class="border-t border-slate-800 pt-2">
336
+ <UCheckbox
337
+ :model-value="config.previewEnabled === true"
338
+ :label="t('inspector.frontendConfig.previewEnabled')"
339
+ size="xs"
340
+ @update:model-value="
341
+ (v: boolean | 'indeterminate') => save({ previewEnabled: v === true ? true : undefined })
342
+ "
343
+ />
344
+ <p class="mt-1 text-[11px] leading-snug text-slate-500">
345
+ {{ t('inspector.frontendConfig.previewHint') }}
346
+ </p>
347
+ </div>
348
+ </div>
349
+ </template>
@@ -30,6 +30,13 @@ export type {
30
30
  InstanceSize,
31
31
  ProvisionType,
32
32
  ServiceProvisioning,
33
+ FrontendConfig,
34
+ FrontendBackendBinding,
35
+ FrontendBackendSource,
36
+ FrontendBranch,
37
+ FrontendPackageManager,
38
+ FrontendServeMode,
39
+ FrontendEnvInjection,
33
40
  AgentConfigOption,
34
41
  AgentConfigDescriptor,
35
42
  TestConcernSeverity,
@@ -415,6 +415,34 @@
415
415
  "manageBoard": "Manage this board's fragment library…",
416
416
  "manageAccount": "Manage account fragments…"
417
417
  },
418
+ "frontendConfig": {
419
+ "title": "Frontend",
420
+ "hint": "How to build, serve, and mock this frontend for a self-contained UI test. Bindings below link it to the backend services it calls.",
421
+ "packageManager": "Package manager",
422
+ "installCommand": "Install command",
423
+ "buildScript": "Build script",
424
+ "outputDir": "Output directory",
425
+ "serveMode": "Serve mode",
426
+ "serveStatic": "Static",
427
+ "serveCommand": "Command",
428
+ "serveModeHint": "Static serves the build output directory; Command runs a package.json script (e.g. preview).",
429
+ "serveScript": "Serve script",
430
+ "servePort": "Serve port",
431
+ "mockMappingsPath": "Mock mappings path",
432
+ "envInjection": "Env injection",
433
+ "envBuild": "Build-time",
434
+ "envRuntime": "Runtime",
435
+ "envInjectionHint": "Build-time injects the backend URLs as build env vars; Runtime writes them into a window.env shim.",
436
+ "previewEnabled": "Enable browsable preview",
437
+ "previewHint": "Keeps a served preview running with a clickable URL. Local and Node runtimes only.",
438
+ "bindings": {
439
+ "title": "Backend bindings",
440
+ "hint": "Each env var the frontend reads for an upstream URL, and where it resolves: a bound service's ephemeral environment, or a WireMock stub.",
441
+ "mock": "Mock (WireMock)",
442
+ "remove": "Remove binding",
443
+ "empty": "No backend bindings. Add one to point an env var at a service or a mock."
444
+ }
445
+ },
418
446
  "releaseHealth": {
419
447
  "title": "Post-release health",
420
448
  "clear": "Clear",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "Gestionar la biblioteca de fragmentos de este tablero…",
379
379
  "manageAccount": "Gestionar los fragmentos de la cuenta…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "Frontend",
383
+ "hint": "Cómo compilar, servir y simular este frontend para una prueba de interfaz autónoma. Las vinculaciones de abajo lo conectan con los servicios de backend a los que llama.",
384
+ "packageManager": "Gestor de paquetes",
385
+ "installCommand": "Comando de instalación",
386
+ "buildScript": "Script de compilación",
387
+ "outputDir": "Directorio de salida",
388
+ "serveMode": "Modo de servicio",
389
+ "serveStatic": "Estático",
390
+ "serveCommand": "Comando",
391
+ "serveModeHint": "Estático sirve el directorio de salida de la compilación; Comando ejecuta un script de package.json (p. ej. preview).",
392
+ "serveScript": "Script de servicio",
393
+ "servePort": "Puerto de servicio",
394
+ "mockMappingsPath": "Ruta de asignaciones de simulación",
395
+ "envInjection": "Inyección de variables de entorno",
396
+ "envBuild": "En compilación",
397
+ "envRuntime": "En ejecución",
398
+ "envInjectionHint": "En compilación inyecta las URL del backend como variables de entorno de compilación; En ejecución las escribe en un shim de window.env.",
399
+ "previewEnabled": "Activar vista previa navegable",
400
+ "previewHint": "Mantiene una vista previa servida en ejecución con una URL clicable. Solo en los entornos Local y Node.",
401
+ "bindings": {
402
+ "title": "Vinculaciones del backend",
403
+ "hint": "Cada variable de entorno que el frontend lee para una URL upstream, y dónde se resuelve: el entorno efímero de un servicio vinculado, o un stub de WireMock.",
404
+ "mock": "Simulación (WireMock)",
405
+ "remove": "Eliminar vinculación",
406
+ "empty": "No hay vinculaciones de backend. Añade una para apuntar una variable de entorno a un servicio o a una simulación."
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "Salud posterior al lanzamiento",
383
411
  "clear": "Limpiar",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "Gérer la bibliothèque de fragments de ce tableau…",
379
379
  "manageAccount": "Gérer les fragments du compte…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "Frontend",
383
+ "hint": "Comment compiler, servir et simuler ce frontend pour un test d'interface autonome. Les liaisons ci-dessous le relient aux services de backend qu'il appelle.",
384
+ "packageManager": "Gestionnaire de paquets",
385
+ "installCommand": "Commande d'installation",
386
+ "buildScript": "Script de compilation",
387
+ "outputDir": "Répertoire de sortie",
388
+ "serveMode": "Mode de service",
389
+ "serveStatic": "Statique",
390
+ "serveCommand": "Commande",
391
+ "serveModeHint": "Statique sert le répertoire de sortie de la compilation ; Commande exécute un script de package.json (p. ex. preview).",
392
+ "serveScript": "Script de service",
393
+ "servePort": "Port de service",
394
+ "mockMappingsPath": "Chemin des mappages de simulation",
395
+ "envInjection": "Injection de variables d'environnement",
396
+ "envBuild": "À la compilation",
397
+ "envRuntime": "À l'exécution",
398
+ "envInjectionHint": "À la compilation, injecte les URL du backend comme variables d'environnement de compilation ; À l'exécution, les écrit dans un shim window.env.",
399
+ "previewEnabled": "Activer l'aperçu navigable",
400
+ "previewHint": "Maintient un aperçu servi en cours d'exécution avec une URL cliquable. Uniquement pour les runtimes Local et Node.",
401
+ "bindings": {
402
+ "title": "Liaisons du backend",
403
+ "hint": "Chaque variable d'environnement que le frontend lit pour une URL en amont, et où elle se résout : l'environnement éphémère d'un service lié, ou un stub WireMock.",
404
+ "mock": "Simulation (WireMock)",
405
+ "remove": "Supprimer la liaison",
406
+ "empty": "Aucune liaison de backend. Ajoutez-en une pour pointer une variable d'environnement vers un service ou une simulation."
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "Santé post-déploiement",
383
411
  "clear": "Effacer",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "נהל את ספריית הקטעים של לוח זה…",
379
379
  "manageAccount": "נהל קטעי חשבון…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "Frontend",
383
+ "hint": "כיצד לבנות, להגיש ולדמות את ה-frontend הזה לבדיקת ממשק עצמאית. הקישורים למטה מחברים אותו לשירותי ה-backend שהוא קורא אליהם.",
384
+ "packageManager": "מנהל חבילות",
385
+ "installCommand": "פקודת התקנה",
386
+ "buildScript": "סקריפט בנייה",
387
+ "outputDir": "תיקיית פלט",
388
+ "serveMode": "מצב הגשה",
389
+ "serveStatic": "סטטי",
390
+ "serveCommand": "פקודה",
391
+ "serveModeHint": "סטטי מגיש את תיקיית פלט הבנייה; פקודה מריצה סקריפט מ-package.json (למשל preview).",
392
+ "serveScript": "סקריפט הגשה",
393
+ "servePort": "פורט הגשה",
394
+ "mockMappingsPath": "נתיב מיפויי הדמיה",
395
+ "envInjection": "הזרקת משתני סביבה",
396
+ "envBuild": "בזמן בנייה",
397
+ "envRuntime": "בזמן ריצה",
398
+ "envInjectionHint": "בזמן בנייה מזריק את כתובות ה-backend כמשתני סביבה של בנייה; בזמן ריצה כותב אותן ל-shim של window.env.",
399
+ "previewEnabled": "הפעל תצוגה מקדימה לגלישה",
400
+ "previewHint": "משאיר תצוגה מקדימה מוגשת פועלת עם כתובת URL לחיצה. רק בסביבות Local ו-Node.",
401
+ "bindings": {
402
+ "title": "קישורי backend",
403
+ "hint": "כל משתנה סביבה שה-frontend קורא עבור כתובת URL במעלה הזרם, והיכן הוא נפתר: הסביבה הזמנית של שירות מקושר, או stub של WireMock.",
404
+ "mock": "הדמיה (WireMock)",
405
+ "remove": "הסר קישור",
406
+ "empty": "אין קישורי backend. הוסף אחד כדי להפנות משתנה סביבה לשירות או להדמיה."
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "תקינות לאחר שחרור",
383
411
  "clear": "נקה",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "このボードのフラグメントライブラリを管理…",
379
379
  "manageAccount": "アカウントのフラグメントを管理…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "フロントエンド",
383
+ "hint": "自己完結型の UI テストのために、このフロントエンドをビルド、配信、モックする方法。下のバインディングは、呼び出すバックエンドサービスとフロントエンドをつなぎます。",
384
+ "packageManager": "パッケージマネージャー",
385
+ "installCommand": "インストールコマンド",
386
+ "buildScript": "ビルドスクリプト",
387
+ "outputDir": "出力ディレクトリ",
388
+ "serveMode": "配信モード",
389
+ "serveStatic": "静的",
390
+ "serveCommand": "コマンド",
391
+ "serveModeHint": "静的はビルド出力ディレクトリを配信します。コマンドは package.json のスクリプト(例: preview)を実行します。",
392
+ "serveScript": "配信スクリプト",
393
+ "servePort": "配信ポート",
394
+ "mockMappingsPath": "モックマッピングのパス",
395
+ "envInjection": "環境変数の注入",
396
+ "envBuild": "ビルド時",
397
+ "envRuntime": "ランタイム",
398
+ "envInjectionHint": "ビルド時はバックエンドの URL をビルド環境変数として注入します。ランタイムは window.env シムに書き込みます。",
399
+ "previewEnabled": "閲覧可能なプレビューを有効にする",
400
+ "previewHint": "クリック可能な URL 付きの配信プレビューを実行し続けます。Local および Node ランタイムのみ。",
401
+ "bindings": {
402
+ "title": "バックエンドバインディング",
403
+ "hint": "フロントエンドがアップストリーム URL のために読み取る各環境変数と、その解決先: バインドされたサービスの一時環境、または WireMock スタブ。",
404
+ "mock": "モック(WireMock)",
405
+ "remove": "バインディングを削除",
406
+ "empty": "バックエンドバインディングがありません。追加して、環境変数をサービスまたはモックに向けてください。"
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "リリース後の健全性",
383
411
  "clear": "クリア",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "Zarządzaj biblioteką fragmentów tej tablicy…",
379
379
  "manageAccount": "Zarządzaj fragmentami konta…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "Frontend",
383
+ "hint": "Jak zbudować, uruchomić i zamockować ten frontend na potrzeby samodzielnego testu interfejsu. Powiązania poniżej łączą go z usługami backendu, które wywołuje.",
384
+ "packageManager": "Menedżer pakietów",
385
+ "installCommand": "Polecenie instalacji",
386
+ "buildScript": "Skrypt budowania",
387
+ "outputDir": "Katalog wyjściowy",
388
+ "serveMode": "Tryb serwowania",
389
+ "serveStatic": "Statyczny",
390
+ "serveCommand": "Polecenie",
391
+ "serveModeHint": "Statyczny serwuje katalog wyjściowy budowania; Polecenie uruchamia skrypt z package.json (np. preview).",
392
+ "serveScript": "Skrypt serwowania",
393
+ "servePort": "Port serwowania",
394
+ "mockMappingsPath": "Ścieżka mapowań mocków",
395
+ "envInjection": "Wstrzykiwanie zmiennych środowiskowych",
396
+ "envBuild": "W czasie budowania",
397
+ "envRuntime": "W czasie działania",
398
+ "envInjectionHint": "W czasie budowania wstrzykuje adresy URL backendu jako zmienne środowiskowe budowania; W czasie działania zapisuje je w shimie window.env.",
399
+ "previewEnabled": "Włącz przeglądalny podgląd",
400
+ "previewHint": "Utrzymuje działający serwowany podgląd z klikalnym adresem URL. Tylko środowiska Local i Node.",
401
+ "bindings": {
402
+ "title": "Powiązania backendu",
403
+ "hint": "Każda zmienna środowiskowa, którą frontend odczytuje dla adresu URL nadrzędnego, oraz miejsce jej rozwiązania: efemeryczne środowisko powiązanej usługi lub zaślepka WireMock.",
404
+ "mock": "Mock (WireMock)",
405
+ "remove": "Usuń powiązanie",
406
+ "empty": "Brak powiązań backendu. Dodaj jedno, aby skierować zmienną środowiskową do usługi lub mocka."
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "Kondycja po wydaniu",
383
411
  "clear": "Wyczyść",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "Bu pano'nun fragman kütüphanesini yönet…",
379
379
  "manageAccount": "Hesap fragmanlarını yönet…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "Frontend",
383
+ "hint": "Bağımsız bir arayüz testi için bu frontend'in nasıl derleneceği, sunulacağı ve taklit edileceği. Aşağıdaki bağlamalar onu çağırdığı backend hizmetlerine bağlar.",
384
+ "packageManager": "Paket yöneticisi",
385
+ "installCommand": "Kurulum komutu",
386
+ "buildScript": "Derleme betiği",
387
+ "outputDir": "Çıktı dizini",
388
+ "serveMode": "Sunma modu",
389
+ "serveStatic": "Statik",
390
+ "serveCommand": "Komut",
391
+ "serveModeHint": "Statik, derleme çıktı dizinini sunar; Komut, bir package.json betiğini (örn. preview) çalıştırır.",
392
+ "serveScript": "Sunma betiği",
393
+ "servePort": "Sunma bağlantı noktası",
394
+ "mockMappingsPath": "Taklit eşleme yolu",
395
+ "envInjection": "Ortam değişkeni enjeksiyonu",
396
+ "envBuild": "Derleme zamanı",
397
+ "envRuntime": "Çalışma zamanı",
398
+ "envInjectionHint": "Derleme zamanı, backend URL'lerini derleme ortam değişkenleri olarak enjekte eder; Çalışma zamanı, bunları bir window.env shim'ine yazar.",
399
+ "previewEnabled": "Göz atılabilir önizlemeyi etkinleştir",
400
+ "previewHint": "Tıklanabilir bir URL ile sunulan bir önizlemeyi çalışır durumda tutar. Yalnızca Local ve Node çalışma zamanları.",
401
+ "bindings": {
402
+ "title": "Backend bağlamaları",
403
+ "hint": "Frontend'in bir üst akış URL'si için okuduğu her ortam değişkeni ve nerede çözümlendiği: bağlı bir hizmetin geçici ortamı veya bir WireMock saplaması.",
404
+ "mock": "Taklit (WireMock)",
405
+ "remove": "Bağlamayı kaldır",
406
+ "empty": "Backend bağlaması yok. Bir ortam değişkenini bir hizmete veya taklide yönlendirmek için bir tane ekleyin."
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "Sürüm sonrası sağlık",
383
411
  "clear": "Temizle",
@@ -378,6 +378,34 @@
378
378
  "manageBoard": "Керувати бібліотекою фрагментів цієї дошки…",
379
379
  "manageAccount": "Керувати фрагментами облікового запису…"
380
380
  },
381
+ "frontendConfig": {
382
+ "title": "Frontend",
383
+ "hint": "Як зібрати, обслуговувати та мокувати цей frontend для автономного тесту інтерфейсу. Прив'язки нижче з'єднують його з сервісами backend, які він викликає.",
384
+ "packageManager": "Менеджер пакетів",
385
+ "installCommand": "Команда встановлення",
386
+ "buildScript": "Скрипт збірки",
387
+ "outputDir": "Каталог виводу",
388
+ "serveMode": "Режим обслуговування",
389
+ "serveStatic": "Статичний",
390
+ "serveCommand": "Команда",
391
+ "serveModeHint": "Статичний обслуговує каталог виводу збірки; Команда запускає скрипт з package.json (напр. preview).",
392
+ "serveScript": "Скрипт обслуговування",
393
+ "servePort": "Порт обслуговування",
394
+ "mockMappingsPath": "Шлях зіставлень моків",
395
+ "envInjection": "Впровадження змінних середовища",
396
+ "envBuild": "Під час збірки",
397
+ "envRuntime": "Під час виконання",
398
+ "envInjectionHint": "Під час збірки впроваджує URL-адреси backend як змінні середовища збірки; Під час виконання записує їх у shim window.env.",
399
+ "previewEnabled": "Увімкнути переглядний попередній перегляд",
400
+ "previewHint": "Підтримує запущений обслуговуваний попередній перегляд із клікабельним URL. Лише середовища Local і Node.",
401
+ "bindings": {
402
+ "title": "Прив'язки backend",
403
+ "hint": "Кожна змінна середовища, яку frontend читає для висхідного URL, і де вона розв'язується: тимчасове середовище прив'язаного сервісу або заглушка WireMock.",
404
+ "mock": "Мок (WireMock)",
405
+ "remove": "Видалити прив'язку",
406
+ "empty": "Немає прив'язок backend. Додайте одну, щоб спрямувати змінну середовища на сервіс або мок."
407
+ }
408
+ },
381
409
  "releaseHealth": {
382
410
  "title": "Стан після релізу",
383
411
  "clear": "Очистити",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.69.1",
3
+ "version": "0.70.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.75.0"
37
+ "@cat-factory/contracts": "0.76.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",