@cat-factory/app 0.84.0 → 0.86.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.
@@ -16,7 +16,7 @@ import { DOC_KINDS } from '~/types/domain'
16
16
  import ContextDocumentPicker from '~/components/documents/ContextDocumentPicker.vue'
17
17
  import ContextIssuePicker from '~/components/tasks/ContextIssuePicker.vue'
18
18
  import { mergePresetOptionLabel, mergePresetThresholds } from '~/utils/mergePreset'
19
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
19
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
20
20
 
21
21
  const ui = useUiStore()
22
22
  const board = useBoardStore()
@@ -197,9 +197,10 @@ const selectedModelPresetLabel = computed(() => {
197
197
  })
198
198
 
199
199
  // Hide UI-testing pipelines (`tester-ui` / `visual-confirmation`) when the target frame has no
200
- // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate).
200
+ // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate). Also
201
+ // hide `'recurring'`-only pipelines: a one-off task start of one is refused at run start.
201
202
  const selectablePipelines = computed(() =>
202
- pipelines.pipelines.filter((p) => pipelineAllowedForFrame(p, frame.value, board.blocks)),
203
+ pipelines.pipelines.filter((p) => pipelineAllowedForManualStart(p, frame.value, board.blocks)),
203
204
  )
204
205
  const pipelineMenu = computed(() => [
205
206
  [
@@ -6,7 +6,7 @@
6
6
  // pipeline is picked, the workspace issue-tracker choice is surfaced inline (it is
7
7
  // where that pipeline files its ticket) and saved alongside.
8
8
  import type { Recurrence, ScheduleTemplate } from '~/types/recurring'
9
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
9
+ import { pipelineAllowedForSchedule } from '~/utils/pipeline'
10
10
 
11
11
  const ui = useUiStore()
12
12
  const board = useBoardStore()
@@ -32,6 +32,9 @@ const description = ref('')
32
32
  const pipelineId = ref('')
33
33
  const saving = ref(false)
34
34
  const recurrence = ref<Recurrence>(defaultRecurrence())
35
+ // On-demand: no cadence, fires only via "run now". Because a person is present at fire time,
36
+ // its block may use an individual-usage subscription model (which a cadence schedule can't).
37
+ const onDemand = ref(false)
35
38
 
36
39
  // Tracker config (only relevant when the tech-debt pipeline is picked).
37
40
  const trackerKind = ref<'github' | 'jira' | 'linear' | null>(null)
@@ -49,8 +52,9 @@ function defaultRecurrence(): Recurrence {
49
52
  }
50
53
 
51
54
  // Hide UI-testing pipelines when the frame has no UI to exercise — they'd be refused at run start.
55
+ // Also hide `'one-off'`-only pipelines: attaching one to a schedule is refused server-side.
52
56
  const selectablePipelines = computed(() =>
53
- pipelines.pipelines.filter((p) => pipelineAllowedForFrame(p, frame.value, board.blocks)),
57
+ pipelines.pipelines.filter((p) => pipelineAllowedForSchedule(p, frame.value, board.blocks)),
54
58
  )
55
59
  const pipelineMenu = computed(() => [
56
60
  selectablePipelines.value.map((p) => ({
@@ -83,6 +87,7 @@ watch(open, (isOpen) => {
83
87
  pipelines.pipelines[0]?.id ??
84
88
  ''
85
89
  recurrence.value = defaultRecurrence()
90
+ onDemand.value = false
86
91
  saving.value = false
87
92
  trackerKind.value = tracker.settings.tracker
88
93
  jiraProjectKey.value = tracker.settings.jiraProjectKey ?? ''
@@ -110,7 +115,9 @@ async function add() {
110
115
  pipelineId: pipelineId.value,
111
116
  template: template.value,
112
117
  name: name.value.trim(),
113
- recurrence: recurrence.value,
118
+ // An on-demand schedule carries no cadence; a scheduled one sends its recurrence.
119
+ onDemand: onDemand.value,
120
+ ...(onDemand.value ? {} : { recurrence: recurrence.value }),
114
121
  ...(description.value.trim() ? { description: description.value.trim() } : {}),
115
122
  })
116
123
  ui.closeAddRecurring()
@@ -173,7 +180,15 @@ async function add() {
173
180
  />
174
181
  </UFormField>
175
182
 
176
- <RecurringRecurrenceEditor v-model="recurrence" />
183
+ <div class="flex items-start gap-2 rounded-lg border border-slate-800 p-3">
184
+ <USwitch v-model="onDemand" size="sm" class="mt-0.5" />
185
+ <div class="space-y-0.5">
186
+ <p class="text-xs font-medium text-slate-200">{{ t('board.recurring.onDemand') }}</p>
187
+ <p class="text-[11px] text-slate-500">{{ t('board.recurring.onDemandHint') }}</p>
188
+ </div>
189
+ </div>
190
+
191
+ <RecurringRecurrenceEditor v-if="!onDemand" v-model="recurrence" />
177
192
 
178
193
  <div v-if="isTechDebt" class="space-y-3 rounded-lg border border-slate-800 p-3">
179
194
  <p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
@@ -2,7 +2,7 @@
2
2
  import { onKeyStroke } from '@vueuse/core'
3
3
  import type { Block } from '~/types/domain'
4
4
  import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
5
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
5
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
6
6
  import PipelineProgress from '~/components/pipeline/PipelineProgress.vue'
7
7
 
8
8
  const board = useBoardStore()
@@ -27,11 +27,12 @@ const deps = computed(() =>
27
27
  (block.value?.dependsOn ?? []).map((id) => board.getBlock(id)).filter((b): b is Block => !!b),
28
28
  )
29
29
 
30
- // Hide UI-testing pipelines when this block's frame has no UI to exercise (see the backend gate).
30
+ // Hide UI-testing pipelines when this block's frame has no UI to exercise, and `'recurring'`-only
31
+ // pipelines (a manual run of one is refused server-side) — see the backend gate.
31
32
  const runMenu = computed(() => {
32
33
  const frame = block.value ? board.serviceOf(block.value) : undefined
33
34
  return pipelines.pipelines
34
- .filter((p) => pipelineAllowedForFrame(p, frame, board.blocks))
35
+ .filter((p) => pipelineAllowedForManualStart(p, frame, board.blocks))
35
36
  .map((p) => ({
36
37
  label: p.name,
37
38
  icon: 'i-lucide-play',
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { Block, BlockStatus } from '~/types/domain'
3
3
  import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
4
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
4
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
5
5
  import TaskContextDocs from '~/components/documents/TaskContextDocs.vue'
6
6
  import TaskContextIssues from '~/components/tasks/TaskContextIssues.vue'
7
7
  import TaskAgentConfig from '~/components/panels/inspector/TaskAgentConfig.vue'
@@ -169,12 +169,13 @@ const taskBranchUrl = computed(() => {
169
169
  return base ? `${base}/tree/${pr.branch}` : null
170
170
  })
171
171
 
172
- // Hide UI-testing pipelines when this block's frame has no UI to exercise they'd be refused
173
- // at run start (see utils/pipeline + the backend gate).
172
+ // Hide UI-testing pipelines when this block's frame has no UI to exercise, and `'recurring'`-only
173
+ // pipelines (a manual run of one is refused server-side) they'd be refused at run start (see
174
+ // utils/pipeline + the backend gate).
174
175
  const runMenu = computed(() => {
175
176
  const frame = block.value ? board.serviceOf(block.value) : undefined
176
177
  return pipelines.pipelines
177
- .filter((p) => pipelineAllowedForFrame(p, frame, board.blocks))
178
+ .filter((p) => pipelineAllowedForManualStart(p, frame, board.blocks))
178
179
  .map((p) => ({
179
180
  label: p.name,
180
181
  icon: 'i-lucide-play',
@@ -150,8 +150,18 @@ function fmtTime(ms: number) {
150
150
  <UIcon name="i-lucide-repeat" class="h-3.5 w-3.5" />
151
151
  {{ t('inspector.recurring.title') }}
152
152
  </span>
153
- <UBadge :color="schedule.enabled ? 'primary' : 'neutral'" variant="subtle" size="xs">
154
- {{ schedule.enabled ? t('inspector.recurring.active') : t('inspector.recurring.paused') }}
153
+ <UBadge
154
+ :color="schedule.onDemand ? 'primary' : schedule.enabled ? 'primary' : 'neutral'"
155
+ variant="subtle"
156
+ size="xs"
157
+ >
158
+ {{
159
+ schedule.onDemand
160
+ ? t('inspector.recurring.onDemand')
161
+ : schedule.enabled
162
+ ? t('inspector.recurring.active')
163
+ : t('inspector.recurring.paused')
164
+ }}
155
165
  </UBadge>
156
166
  </div>
157
167
 
@@ -159,7 +169,17 @@ function fmtTime(ms: number) {
159
169
  <span class="text-slate-300">{{ pipelineName }}</span>
160
170
  </p>
161
171
 
162
- <template v-if="!editing">
172
+ <!-- On-demand: no cadence, no pause/resume — it fires only when a person triggers it. -->
173
+ <template v-if="schedule.onDemand">
174
+ <p class="text-[11px] text-slate-500">{{ t('inspector.recurring.onDemandHint') }}</p>
175
+ <div class="flex flex-wrap gap-1.5 pt-1">
176
+ <UButton size="xs" variant="soft" icon="i-lucide-play" :loading="busy" @click="runNow">
177
+ {{ t('inspector.recurring.runNow') }}
178
+ </UButton>
179
+ </div>
180
+ </template>
181
+
182
+ <template v-else-if="!editing">
163
183
  <p class="text-[11px] text-slate-400">{{ describeCadence(schedule.recurrence) }}</p>
164
184
  <p class="text-[11px] text-slate-500">
165
185
  {{ t('inspector.recurring.nextRun', { time: fmtTime(schedule.nextRunAt) }) }}
@@ -4,7 +4,7 @@ import { connectionNeighborIds } from '@cat-factory/contracts'
4
4
  import type { Block } from '~/types/domain'
5
5
  import type { WritebackOverride } from '~/types/tracker'
6
6
  import { mergePresetOptionLabel, mergePresetThresholds } from '~/utils/mergePreset'
7
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
7
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
8
8
  import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
9
9
 
10
10
  const props = defineProps<{ block: Block }>()
@@ -132,11 +132,14 @@ function setModelPreset(id: string) {
132
132
  const selectedPipeline = computed(() =>
133
133
  props.block.pipelineId ? pipelines.getPipeline(props.block.pipelineId) : undefined,
134
134
  )
135
- // Hide UI-testing pipelines when this task's frame has no UI to exercise they'd be refused at
136
- // run start (see utils/pipeline + the backend gate).
135
+ // Hide UI-testing pipelines when this task's frame has no UI to exercise, and `'recurring'`-only
136
+ // pipelines (the task's manual Run control can't start one) they'd be refused at run start
137
+ // (see utils/pipeline + the backend gate).
137
138
  const taskFrame = computed(() => board.serviceOf(props.block))
138
139
  const selectablePipelines = computed(() =>
139
- pipelines.pipelines.filter((p) => pipelineAllowedForFrame(p, taskFrame.value, board.blocks)),
140
+ pipelines.pipelines.filter((p) =>
141
+ pipelineAllowedForManualStart(p, taskFrame.value, board.blocks),
142
+ ),
140
143
  )
141
144
  const pipelineMenu = computed(() => [
142
145
  [
@@ -21,7 +21,7 @@ import type { ApiContext, Position } from './context'
21
21
  type CreateScheduleBody = NonNullable<SendParams<typeof createScheduleContract>['body']>
22
22
 
23
23
  /** Recurring (scheduled) pipelines + the in-org shared-service mount catalog. */
24
- export function recurringApi({ send, ws }: ApiContext) {
24
+ export function recurringApi({ send, sendWith, ws, pwHeaders }: ApiContext) {
25
25
  return {
26
26
  // ---- recurring pipelines (scheduled runs against a service) -----------
27
27
  listRecurringPipelines: (workspaceId: string) =>
@@ -46,8 +46,13 @@ export function recurringApi({ send, ws }: ApiContext) {
46
46
  pathParams: { scheduleId: id },
47
47
  }),
48
48
 
49
- runScheduleNow: (workspaceId: string, id: string) =>
50
- send(runScheduleNowContract, { pathPrefix: ws(workspaceId), pathParams: { scheduleId: id } }),
49
+ // On-demand schedules may target an individual-usage model, so run-now carries the
50
+ // initiator's personal password (like a manual start) prompted + retried on a 428.
51
+ runScheduleNow: (workspaceId: string, id: string, password?: string) =>
52
+ sendWith(pwHeaders(password), runScheduleNowContract, {
53
+ pathPrefix: ws(workspaceId),
54
+ pathParams: { scheduleId: id },
55
+ }),
51
56
 
52
57
  // ---- in-org shared services (mount/unmount + org catalog) -------------
53
58
  // The services this workspace mounts, and the org catalog it can mount from. A 503
@@ -80,12 +80,20 @@ export const useRecurringPipelinesStore = defineStore('recurringPipelines', () =
80
80
  }
81
81
  }
82
82
 
83
- async function runNow(id: string) {
83
+ /**
84
+ * Fire a schedule now. An on-demand schedule may target an individual-usage model, so the
85
+ * initiator's personal password is supplied transparently from the cache and prompted via
86
+ * the credential modal (then retried) when the server replies 428. Returns false when the
87
+ * user cancels the password prompt; true once the fire is accepted.
88
+ */
89
+ async function runNow(id: string): Promise<boolean> {
84
90
  const ws = useWorkspaceStore()
85
- const schedule = await api.runScheduleNow(ws.requireId(), id)
86
- await loadRuns(id)
87
- await ws.refresh()
88
- return schedule
91
+ const personal = usePersonalSubscriptionsStore()
92
+ return personal.withCredential(async (password) => {
93
+ await api.runScheduleNow(ws.requireId(), id, password)
94
+ await loadRuns(id)
95
+ await ws.refresh()
96
+ })
89
97
  }
90
98
 
91
99
  /** Fetch (and cache) a schedule's run history for the inspector. */
@@ -20,3 +20,33 @@ export function pipelineAllowedForFrame(
20
20
  ): boolean {
21
21
  return !pipelineHasVisualStep(pipeline) || frameAllowsVisualPipeline(frame, blocks)
22
22
  }
23
+
24
+ // Launch-availability filters, the surface counterpart to the backend's start-origin gate (a
25
+ // `'recurring'`-only pipeline can't be started as a one-off manual task, and a `'one-off'`-only
26
+ // pipeline can't be attached to a schedule). `availability` absent ⇒ `'both'` (unrestricted), so
27
+ // legacy/unset pipelines pass both. Composed with {@link pipelineAllowedForFrame} at each picker.
28
+
29
+ /**
30
+ * Whether `pipeline` may be started as a MANUAL one-off task run (the board/inspector Run menus,
31
+ * the add-task modal, the task run-settings default). Excludes `'recurring'`-only pipelines the
32
+ * backend would refuse.
33
+ */
34
+ export function pipelineAllowedForManualStart(
35
+ pipeline: Pipeline,
36
+ frame: Block | undefined,
37
+ blocks: readonly Block[],
38
+ ): boolean {
39
+ return pipeline.availability !== 'recurring' && pipelineAllowedForFrame(pipeline, frame, blocks)
40
+ }
41
+
42
+ /**
43
+ * Whether `pipeline` may be attached to a RECURRING schedule (the recurring-pipeline modal).
44
+ * Excludes `'one-off'`-only pipelines the backend would refuse.
45
+ */
46
+ export function pipelineAllowedForSchedule(
47
+ pipeline: Pipeline,
48
+ frame: Block | undefined,
49
+ blocks: readonly Block[],
50
+ ): boolean {
51
+ return pipeline.availability !== 'one-off' && pipelineAllowedForFrame(pipeline, frame, blocks)
52
+ }
@@ -217,7 +217,9 @@
217
217
  "linearTeamId": "Linear team id",
218
218
  "footerHint": "A single recurring task is added inside the service; each run replaces the last. Its run history is visible in the inspector.",
219
219
  "submit": "Add recurring pipeline",
220
- "addFailedTitle": "Could not add recurring pipeline"
220
+ "addFailedTitle": "Could not add recurring pipeline",
221
+ "onDemand": "On-demand (manual only)",
222
+ "onDemandHint": "Runs only when you trigger it, with no schedule. Because you are present each time, its task may use an individual-usage subscription model."
221
223
  },
222
224
  "failure": {
223
225
  "containerFailedToStart": "Container failed to start",
@@ -481,7 +483,9 @@
481
483
  "skipped": "Skipped"
482
484
  },
483
485
  "updateFailed": "Could not update schedule",
484
- "runNowFailed": "Could not run now"
486
+ "runNowFailed": "Could not run now",
487
+ "onDemand": "On-demand",
488
+ "onDemandHint": "Manual only. Runs when you trigger it, and may use an individual-usage subscription model."
485
489
  },
486
490
  "fragments": {
487
491
  "serviceTitle": "Service best practices",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "ID del equipo de Linear",
197
197
  "footerHint": "Se añade una única tarea recurrente dentro del servicio; cada ejecución reemplaza a la anterior. Su historial de ejecuciones es visible en el inspector.",
198
198
  "submit": "Añadir pipeline recurrente",
199
- "addFailedTitle": "No se pudo añadir la pipeline recurrente"
199
+ "addFailedTitle": "No se pudo añadir la pipeline recurrente",
200
+ "onDemand": "Bajo demanda (solo manual)",
201
+ "onDemandHint": "Se ejecuta solo cuando lo activas, sin programación. Como estás presente cada vez, su tarea puede usar un modelo de suscripción de uso individual."
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "El contenedor no pudo iniciarse",
@@ -438,7 +440,9 @@
438
440
  "skipped": "Omitida"
439
441
  },
440
442
  "updateFailed": "No se pudo actualizar la programación",
441
- "runNowFailed": "No se pudo ejecutar ahora"
443
+ "runNowFailed": "No se pudo ejecutar ahora",
444
+ "onDemand": "Bajo demanda",
445
+ "onDemandHint": "Solo manual. Se ejecuta cuando lo activas y puede usar un modelo de suscripción de uso individual."
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "Buenas prácticas del servicio",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "ID d’équipe Linear",
197
197
  "footerHint": "Une seule tâche récurrente est ajoutée dans le service ; chaque exécution remplace la précédente. Son historique d’exécutions est visible dans l’inspecteur.",
198
198
  "submit": "Ajouter la pipeline récurrente",
199
- "addFailedTitle": "Impossible d’ajouter la pipeline récurrente"
199
+ "addFailedTitle": "Impossible d’ajouter la pipeline récurrente",
200
+ "onDemand": "À la demande (manuel uniquement)",
201
+ "onDemandHint": "Ne s'exécute que lorsque vous le déclenchez, sans planification. Comme vous êtes présent à chaque fois, sa tâche peut utiliser un modèle d'abonnement à usage individuel."
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "Le conteneur n’a pas pu démarrer",
@@ -438,7 +440,9 @@
438
440
  "skipped": "Ignoré"
439
441
  },
440
442
  "updateFailed": "Impossible de mettre à jour la planification",
441
- "runNowFailed": "Impossible d'exécuter maintenant"
443
+ "runNowFailed": "Impossible d'exécuter maintenant",
444
+ "onDemand": "À la demande",
445
+ "onDemandHint": "Manuel uniquement. S'exécute lorsque vous le déclenchez et peut utiliser un modèle d'abonnement à usage individuel."
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "Bonnes pratiques du service",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "מזהה צוות Linear",
197
197
  "footerHint": "משימה מחזורית אחת נוספת בתוך השירות; כל ריצה מחליפה את הקודמת. היסטוריית הריצות שלה מוצגת במפקח.",
198
198
  "submit": "הוסף צינור מחזורי",
199
- "addFailedTitle": "לא ניתן היה להוסיף צינור מחזורי"
199
+ "addFailedTitle": "לא ניתן היה להוסיף צינור מחזורי",
200
+ "onDemand": "לפי דרישה (ידני בלבד)",
201
+ "onDemandHint": "רץ רק כשאתה מפעיל אותו, ללא תזמון. מכיוון שאתה נוכח בכל פעם, המשימה יכולה להשתמש במודל מנוי לשימוש אישי."
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "מכל הקונטיינר נכשל בהפעלה",
@@ -438,7 +440,9 @@
438
440
  "skipped": "דולג"
439
441
  },
440
442
  "updateFailed": "לא ניתן לעדכן את לוח הזמנים",
441
- "runNowFailed": "לא ניתן להריץ עכשיו"
443
+ "runNowFailed": "לא ניתן להריץ עכשיו",
444
+ "onDemand": "לפי דרישה",
445
+ "onDemandHint": "ידני בלבד. רץ כשאתה מפעיל אותו, ויכול להשתמש במודל מנוי לשימוש אישי."
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "מומלצות עבודה לשירות",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "Linear チーム ID",
197
197
  "footerHint": "サービス内に繰り返しタスクが 1 つ追加され、各実行が前回を置き換えます。実行履歴はインスペクターで確認できます。",
198
198
  "submit": "繰り返しパイプラインを追加",
199
- "addFailedTitle": "繰り返しパイプラインを追加できませんでした"
199
+ "addFailedTitle": "繰り返しパイプラインを追加できませんでした",
200
+ "onDemand": "オンデマンド(手動のみ)",
201
+ "onDemandHint": "スケジュールはなく、手動で実行したときのみ動作します。毎回ユーザーが立ち会うため、タスクは個人利用のサブスクリプションモデルを使用できます。"
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "コンテナの起動に失敗しました",
@@ -438,7 +440,9 @@
438
440
  "skipped": "スキップ"
439
441
  },
440
442
  "updateFailed": "スケジュールを更新できませんでした",
441
- "runNowFailed": "今すぐ実行できませんでした"
443
+ "runNowFailed": "今すぐ実行できませんでした",
444
+ "onDemand": "オンデマンド",
445
+ "onDemandHint": "手動のみ。実行したときに動作し、個人利用のサブスクリプションモデルを使用できます。"
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "サービスのベストプラクティス",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "ID zespołu Linear",
197
197
  "footerHint": "W usłudze dodawane jest jedno cykliczne zadanie; każde uruchomienie zastępuje poprzednie. Jego historia uruchomień jest widoczna w inspektorze.",
198
198
  "submit": "Dodaj cykliczny pipeline",
199
- "addFailedTitle": "Nie udało się dodać cyklicznego pipeline’u"
199
+ "addFailedTitle": "Nie udało się dodać cyklicznego pipeline’u",
200
+ "onDemand": "Na żądanie (tylko ręcznie)",
201
+ "onDemandHint": "Uruchamia się tylko po ręcznym wyzwoleniu, bez harmonogramu. Ponieważ za każdym razem jesteś obecny, jego zadanie może korzystać z modelu subskrypcji do użytku indywidualnego."
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "Nie udało się uruchomić kontenera",
@@ -438,7 +440,9 @@
438
440
  "skipped": "Pominięto"
439
441
  },
440
442
  "updateFailed": "Nie udało się zaktualizować harmonogramu",
441
- "runNowFailed": "Nie udało się uruchomić teraz"
443
+ "runNowFailed": "Nie udało się uruchomić teraz",
444
+ "onDemand": "Na żądanie",
445
+ "onDemandHint": "Tylko ręcznie. Uruchamia się po wyzwoleniu i może korzystać z modelu subskrypcji do użytku indywidualnego."
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "Dobre praktyki usługi",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "Linear takım kimliği",
197
197
  "footerHint": "Servisin içine tek bir yinelenen görev eklenir; her çalıştırma bir öncekinin yerini alır. Çalıştırma geçmişi inceleme panelinde görünür.",
198
198
  "submit": "Yinelenen pipeline ekle",
199
- "addFailedTitle": "Yinelenen pipeline eklenemedi"
199
+ "addFailedTitle": "Yinelenen pipeline eklenemedi",
200
+ "onDemand": "İstek üzerine (yalnızca manuel)",
201
+ "onDemandHint": "Yalnızca siz tetiklediğinizde çalışır, zamanlama yoktur. Her seferinde siz hazır bulunduğunuz için görevi bireysel kullanımlı bir abonelik modeli kullanabilir."
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "Konteyner başlatılamadı",
@@ -438,7 +440,9 @@
438
440
  "skipped": "Atlandı"
439
441
  },
440
442
  "updateFailed": "Zamanlama güncellenemedi",
441
- "runNowFailed": "Şimdi çalıştırılamadı"
443
+ "runNowFailed": "Şimdi çalıştırılamadı",
444
+ "onDemand": "İstek üzerine",
445
+ "onDemandHint": "Yalnızca manuel. Siz tetiklediğinizde çalışır ve bireysel kullanımlı bir abonelik modeli kullanabilir."
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "Servis en iyi uygulamaları",
@@ -196,7 +196,9 @@
196
196
  "linearTeamId": "ID команди Linear",
197
197
  "footerHint": "У сервісі додається одне періодичне завдання; кожен запуск замінює попередній. Його історія запусків доступна в інспекторі.",
198
198
  "submit": "Додати періодичний конвеєр",
199
- "addFailedTitle": "Не вдалося додати періодичний конвеєр"
199
+ "addFailedTitle": "Не вдалося додати періодичний конвеєр",
200
+ "onDemand": "За запитом (лише вручну)",
201
+ "onDemandHint": "Запускається лише коли ви його активуєте, без розкладу. Оскільки ви присутні щоразу, його завдання може використовувати модель підписки для індивідуального використання."
200
202
  },
201
203
  "failure": {
202
204
  "containerFailedToStart": "Не вдалося запустити контейнер",
@@ -438,7 +440,9 @@
438
440
  "skipped": "Пропущено"
439
441
  },
440
442
  "updateFailed": "Не вдалося оновити розклад",
441
- "runNowFailed": "Не вдалося запустити зараз"
443
+ "runNowFailed": "Не вдалося запустити зараз",
444
+ "onDemand": "За запитом",
445
+ "onDemandHint": "Лише вручну. Запускається коли ви активуєте, і може використовувати модель підписки для індивідуального використання."
442
446
  },
443
447
  "fragments": {
444
448
  "serviceTitle": "Найкращі практики сервісу",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.84.0",
3
+ "version": "0.86.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.91.0"
37
+ "@cat-factory/contracts": "0.93.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",