@cat-factory/app 0.74.0 → 0.74.2
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,20 +18,46 @@ const props = defineProps<{
|
|
|
18
18
|
* in-repo and a pasted URL / bare issue number resolves to the exact issue.
|
|
19
19
|
*/
|
|
20
20
|
scopeBlockId?: string
|
|
21
|
+
/**
|
|
22
|
+
* Controlled source: when provided the parent owns the selected tracker (via
|
|
23
|
+
* `v-model:source`); omitted, the picker manages it internally (the add-task case).
|
|
24
|
+
*/
|
|
25
|
+
source?: TaskSourceKind
|
|
26
|
+
/**
|
|
27
|
+
* Always render the source selector, even with a single offered tracker — so the
|
|
28
|
+
* user can see *which* tracker is being searched (the "create task from issue"
|
|
29
|
+
* surface, where the source is otherwise invisible). Off by default: the inline
|
|
30
|
+
* add-task picker stays compact and only shows a selector when there's a choice.
|
|
31
|
+
*/
|
|
32
|
+
alwaysShowSource?: boolean
|
|
33
|
+
}>()
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
pick: [item: PendingContext]
|
|
36
|
+
'update:source': [value: TaskSourceKind]
|
|
21
37
|
}>()
|
|
22
|
-
const emit = defineEmits<{ pick: [item: PendingContext] }>()
|
|
23
38
|
|
|
24
39
|
const { t } = useI18n()
|
|
25
40
|
const tasks = useTasksStore()
|
|
26
41
|
|
|
27
42
|
const chosen = computed(() => new Set(props.chosenKeys ?? []))
|
|
28
43
|
|
|
29
|
-
// Source: default to the first offered tracker
|
|
30
|
-
//
|
|
31
|
-
|
|
44
|
+
// Source: default to the first offered tracker. Controlled when the parent passes
|
|
45
|
+
// `source` (write-through to `update:source`), else internal. A selector appears when
|
|
46
|
+
// more than one source is offered, or whenever the parent asks (`alwaysShowSource`).
|
|
47
|
+
const internalSource = ref<TaskSourceKind | undefined>(tasks.offeredSources[0]?.source)
|
|
48
|
+
const source = computed<TaskSourceKind | undefined>({
|
|
49
|
+
get: () => props.source ?? internalSource.value,
|
|
50
|
+
set: (v) => {
|
|
51
|
+
internalSource.value = v
|
|
52
|
+
if (v) emit('update:source', v)
|
|
53
|
+
},
|
|
54
|
+
})
|
|
32
55
|
const sourceItems = computed(() =>
|
|
33
56
|
tasks.offeredSources.map((s) => ({ label: s.label, value: s.source })),
|
|
34
57
|
)
|
|
58
|
+
const showSourceSelect = computed(
|
|
59
|
+
() => sourceItems.value.length > 1 || (props.alwaysShowSource && sourceItems.value.length > 0),
|
|
60
|
+
)
|
|
35
61
|
const descriptor = computed(() => (source.value ? tasks.descriptorFor(source.value) : undefined))
|
|
36
62
|
const searchable = computed(() => descriptor.value?.searchable ?? false)
|
|
37
63
|
|
|
@@ -42,8 +68,10 @@ const searchError = ref<string | null>(null)
|
|
|
42
68
|
|
|
43
69
|
// Debounced search: free text hits the tracker; a query that's clearly a URL/key
|
|
44
70
|
// is left to the explicit "by reference" row below (search won't surface it).
|
|
71
|
+
// Re-scope when `scopeBlockId` changes too (a GitHub search is scoped to the block's
|
|
72
|
+
// repo, so switching the target container re-runs against the new repo).
|
|
45
73
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
46
|
-
watch([query, source], () => {
|
|
74
|
+
watch([query, source, () => props.scopeBlockId], () => {
|
|
47
75
|
if (timer) clearTimeout(timer)
|
|
48
76
|
results.value = []
|
|
49
77
|
searchError.value = null
|
|
@@ -108,8 +136,10 @@ const refRow = computed(() => {
|
|
|
108
136
|
searchRows.value.some((r) => r.externalId === q) ||
|
|
109
137
|
chosen.value.has(keyFor(q))
|
|
110
138
|
if (known) return null
|
|
111
|
-
// Only worth offering when it looks like a reference, not a search phrase
|
|
112
|
-
|
|
139
|
+
// Only worth offering when it looks like a reference, not a search phrase: a URL,
|
|
140
|
+
// an owner/repo#n or #n GitHub ref, or a Jira/Linear-style key (PROJ-123, ENG-42).
|
|
141
|
+
const looksLikeRef =
|
|
142
|
+
q.includes('#') || q.includes('/') || /^https?:\/\//i.test(q) || /^[a-z][a-z0-9]*-\d+$/i.test(q)
|
|
113
143
|
return looksLikeRef ? q : null
|
|
114
144
|
})
|
|
115
145
|
|
|
@@ -173,7 +203,7 @@ onMounted(() => {
|
|
|
173
203
|
<template>
|
|
174
204
|
<div class="space-y-2 rounded-lg border border-slate-800 bg-slate-900/40 p-2">
|
|
175
205
|
<USelect
|
|
176
|
-
v-if="
|
|
206
|
+
v-if="showSourceSelect"
|
|
177
207
|
v-model="source"
|
|
178
208
|
:items="sourceItems"
|
|
179
209
|
size="xs"
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
2
|
+
// Create a board task from a connected tracker's issue. Pick a container (service
|
|
3
|
+
// frame or module), then use the inline picker below to find an issue (search by
|
|
4
|
+
// title, pick an already-imported one, or paste a URL/key) — choosing one opens the
|
|
5
|
+
// prefilled add-task form (title seeded, issue staged as linked context) where the
|
|
6
|
+
// user confirms the pipeline / presets before it's created. This is the same picker
|
|
7
|
+
// the add-task form uses for "context issues", so the two behave identically. A
|
|
8
|
+
// pasted parent/epic reference can instead be spawned as a whole linked task group.
|
|
9
|
+
import type { TaskSourceKind } from '~/types/domain'
|
|
10
|
+
import type { PendingContext } from '~/composables/useContextLinking'
|
|
11
|
+
import ContextIssuePicker from '~/components/tasks/ContextIssuePicker.vue'
|
|
11
12
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
12
13
|
|
|
13
14
|
const { t } = useI18n()
|
|
@@ -24,6 +25,9 @@ const open = computed({
|
|
|
24
25
|
})
|
|
25
26
|
const back = useIntegrationBack(open)
|
|
26
27
|
|
|
28
|
+
// The tracker being browsed. Owned here (not the picker) so the epic action and the
|
|
29
|
+
// ref-input placeholder share the same selected source; passed to the picker via
|
|
30
|
+
// `v-model:source` with `always-show-source` so it's always visible + selectable.
|
|
27
31
|
const source = ref<TaskSourceKind | undefined>(undefined)
|
|
28
32
|
const ref_ = ref('')
|
|
29
33
|
const importing = ref(false)
|
|
@@ -34,60 +38,11 @@ const title = computed(() =>
|
|
|
34
38
|
ui.taskImport?.containerId ? t('tasks.import.titleCreate') : t('tasks.import.titleBrowse'),
|
|
35
39
|
)
|
|
36
40
|
|
|
37
|
-
const sourceItems = computed(() =>
|
|
38
|
-
tasks.offeredSources.map((s) => ({ label: s.label, value: s.source })),
|
|
39
|
-
)
|
|
40
41
|
const descriptor = computed(() => (source.value ? tasks.descriptorFor(source.value) : undefined))
|
|
41
|
-
const searchable = computed(() => descriptor.value?.searchable ?? false)
|
|
42
42
|
|
|
43
|
-
// The container (service frame or module) a new task is created in.
|
|
44
|
-
// scope for the issue search — declared up here so the search watch can read it.
|
|
43
|
+
// The container (service frame or module) a new task is created in.
|
|
45
44
|
const containerId = ref<string | undefined>(undefined)
|
|
46
45
|
|
|
47
|
-
// Browse the tracker by free text so an issue can be turned into a task without
|
|
48
|
-
// knowing its key. Debounced; a created/imported hit also lands in the list below.
|
|
49
|
-
const searchQuery = ref('')
|
|
50
|
-
const searchResults = ref<TaskSearchResult[]>([])
|
|
51
|
-
const searching = ref(false)
|
|
52
|
-
const searchError = ref<string | null>(null)
|
|
53
|
-
|
|
54
|
-
let searchTimer: ReturnType<typeof setTimeout> | undefined
|
|
55
|
-
// Re-run when the chosen container changes too: a GitHub search is scoped to the
|
|
56
|
-
// selected service's repo, so switching containers re-scopes the results.
|
|
57
|
-
watch([searchQuery, source, () => containerId.value], () => {
|
|
58
|
-
if (searchTimer) clearTimeout(searchTimer)
|
|
59
|
-
searchResults.value = []
|
|
60
|
-
searchError.value = null
|
|
61
|
-
const q = searchQuery.value.trim()
|
|
62
|
-
if (!q || !searchable.value) return
|
|
63
|
-
searchTimer = setTimeout(runSearch, 300)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
async function runSearch() {
|
|
67
|
-
const q = searchQuery.value.trim()
|
|
68
|
-
if (!q || !source.value) return
|
|
69
|
-
searching.value = true
|
|
70
|
-
searchError.value = null
|
|
71
|
-
try {
|
|
72
|
-
// Scope to the selected container's repo so hits stay in-repo and a pasted
|
|
73
|
-
// URL / bare issue number resolves to the exact issue.
|
|
74
|
-
searchResults.value = await tasks.search(source.value, q, containerId.value)
|
|
75
|
-
} catch (e) {
|
|
76
|
-
searchResults.value = []
|
|
77
|
-
searchError.value = e instanceof Error ? e.message : String(e)
|
|
78
|
-
} finally {
|
|
79
|
-
searching.value = false
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Search hits not yet imported (imported ones already render in the list below).
|
|
84
|
-
const importedIds = computed(
|
|
85
|
-
() => new Set(tasks.tasks.filter((t) => t.source === source.value).map((t) => t.externalId)),
|
|
86
|
-
)
|
|
87
|
-
const freshHits = computed(() =>
|
|
88
|
-
searchResults.value.filter((r) => !importedIds.value.has(r.externalId)),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
46
|
// Containers a new task can be created in: every service frame and module on the
|
|
92
47
|
// board. Modules are labelled with their parent frame so the choice is unambiguous.
|
|
93
48
|
const containerItems = computed(() =>
|
|
@@ -104,69 +59,28 @@ const containerItems = computed(() =>
|
|
|
104
59
|
watch(open, (isOpen) => {
|
|
105
60
|
if (isOpen) {
|
|
106
61
|
ref_.value = ''
|
|
107
|
-
searchQuery.value = ''
|
|
108
|
-
searchResults.value = []
|
|
109
|
-
searchError.value = null
|
|
110
62
|
source.value = ui.taskImport?.source ?? tasks.offeredSources[0]?.source ?? undefined
|
|
111
|
-
// Opened from a service frame → preselect it as the create-in target
|
|
112
|
-
//
|
|
63
|
+
// Opened from a service frame → preselect it as the create-in target; otherwise
|
|
64
|
+
// fall back to the first container on the board.
|
|
113
65
|
containerId.value = ui.taskImport?.containerId ?? containerItems.value[0]?.value
|
|
114
66
|
tasks.loadTasks().catch(() => {})
|
|
115
67
|
}
|
|
116
68
|
})
|
|
117
69
|
|
|
118
|
-
//
|
|
119
|
-
// and the issue staged as linked context (so agents see its description +
|
|
120
|
-
// The user still confirms pipeline / preset there before the task is
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const prefill: AddTaskPrefill = {
|
|
131
|
-
title: issue.title,
|
|
132
|
-
context: [
|
|
133
|
-
{
|
|
134
|
-
kind: 'task',
|
|
135
|
-
source: source.value,
|
|
136
|
-
externalId: issue.externalId,
|
|
137
|
-
title: `${issue.externalId} · ${issue.title}`,
|
|
138
|
-
subtitle: issue.status || undefined,
|
|
139
|
-
icon: descriptor.value?.icon,
|
|
140
|
-
description: issue.description || undefined,
|
|
141
|
-
needsImport,
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
}
|
|
70
|
+
// Choosing an issue in the picker hands off to the add-task form, prefilled with the
|
|
71
|
+
// issue title and the issue staged as linked context (so agents see its description +
|
|
72
|
+
// comments). The user still confirms pipeline / preset there before the task is
|
|
73
|
+
// created. A search hit / pasted ref carries `needsImport`, so the add-task form
|
|
74
|
+
// resolves its body (by importing) and folds it into the new task's description; an
|
|
75
|
+
// already-imported issue carries its body directly.
|
|
76
|
+
function createFromPick(item: PendingContext) {
|
|
77
|
+
if (!containerId.value) return
|
|
78
|
+
// The picker titles rows as "EXTERNALID · Title"; seed the task with the clean
|
|
79
|
+
// title. A pasted ref has no title (title === the raw ref), so leave it blank for
|
|
80
|
+
// the user to name in the form.
|
|
81
|
+
const seededTitle = item.title === item.externalId ? '' : item.title.replace(/^[^·]+·\s*/, '')
|
|
145
82
|
ui.closeTaskImport()
|
|
146
|
-
ui.openAddTask(containerId.value,
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function doImport() {
|
|
150
|
-
const value = ref_.value.trim()
|
|
151
|
-
if (!value || !source.value) return
|
|
152
|
-
importing.value = true
|
|
153
|
-
try {
|
|
154
|
-
const task = await tasks.importTask(source.value, value)
|
|
155
|
-
ref_.value = ''
|
|
156
|
-
toast.add({
|
|
157
|
-
title: t('tasks.import.imported', { title: task.title }),
|
|
158
|
-
icon: 'i-lucide-file-down',
|
|
159
|
-
})
|
|
160
|
-
} catch (e) {
|
|
161
|
-
toast.add({
|
|
162
|
-
title: t('tasks.import.importFailed'),
|
|
163
|
-
description: e instanceof Error ? e.message : String(e),
|
|
164
|
-
icon: 'i-lucide-triangle-alert',
|
|
165
|
-
color: 'error',
|
|
166
|
-
})
|
|
167
|
-
} finally {
|
|
168
|
-
importing.value = false
|
|
169
|
-
}
|
|
83
|
+
ui.openAddTask(containerId.value, { title: seededTitle, context: [item] })
|
|
170
84
|
}
|
|
171
85
|
|
|
172
86
|
// Spawn the referenced issue as an EPIC: an epic node + a task per child issue (into the
|
|
@@ -227,54 +141,15 @@ async function doSpawnEpic() {
|
|
|
227
141
|
</div>
|
|
228
142
|
</div>
|
|
229
143
|
|
|
144
|
+
<!-- No service frame yet → nowhere to create a task. -->
|
|
145
|
+
<p v-else-if="!containerItems.length" class="text-center text-xs text-slate-500">
|
|
146
|
+
{{ t('tasks.import.needFrameFirst') }}
|
|
147
|
+
</p>
|
|
148
|
+
|
|
230
149
|
<!-- Main form -->
|
|
231
150
|
<div v-else class="space-y-4">
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
</UFormField>
|
|
235
|
-
|
|
236
|
-
<div class="flex items-end gap-2">
|
|
237
|
-
<UFormField :label="descriptor?.refLabel ?? t('tasks.import.refLabel')" class="flex-1">
|
|
238
|
-
<UInput
|
|
239
|
-
v-model="ref_"
|
|
240
|
-
:placeholder="descriptor?.refPlaceholder"
|
|
241
|
-
class="w-full"
|
|
242
|
-
@keydown.enter="doImport"
|
|
243
|
-
/>
|
|
244
|
-
</UFormField>
|
|
245
|
-
<UButton
|
|
246
|
-
color="primary"
|
|
247
|
-
icon="i-lucide-file-down"
|
|
248
|
-
:loading="importing"
|
|
249
|
-
:disabled="!ref_.trim()"
|
|
250
|
-
@click="doImport"
|
|
251
|
-
>
|
|
252
|
-
{{ t('tasks.import.import') }}
|
|
253
|
-
</UButton>
|
|
254
|
-
<UButton
|
|
255
|
-
color="primary"
|
|
256
|
-
variant="soft"
|
|
257
|
-
icon="i-lucide-layers"
|
|
258
|
-
:loading="importing"
|
|
259
|
-
:disabled="!ref_.trim() || !containerId"
|
|
260
|
-
:title="
|
|
261
|
-
containerId
|
|
262
|
-
? t('tasks.import.asEpicTitleReady')
|
|
263
|
-
: t('tasks.import.asEpicTitleNeedsContainer')
|
|
264
|
-
"
|
|
265
|
-
@click="doSpawnEpic"
|
|
266
|
-
>
|
|
267
|
-
{{ t('tasks.import.asEpic') }}
|
|
268
|
-
</UButton>
|
|
269
|
-
</div>
|
|
270
|
-
|
|
271
|
-
<!-- Container for epic children when spawning from a pasted ref (the shared
|
|
272
|
-
"Create tasks in" selector below covers the search-results case). -->
|
|
273
|
-
<UFormField
|
|
274
|
-
v-if="containerItems.length && !freshHits.length"
|
|
275
|
-
:label="t('tasks.import.epicChildrenContainer')"
|
|
276
|
-
class="w-72"
|
|
277
|
-
>
|
|
151
|
+
<!-- Where the new task lands (preselected when opened from a service frame). -->
|
|
152
|
+
<UFormField :label="t('tasks.import.createTasksIn')">
|
|
278
153
|
<USelect
|
|
279
154
|
v-model="containerId"
|
|
280
155
|
:items="containerItems"
|
|
@@ -283,94 +158,52 @@ async function doSpawnEpic() {
|
|
|
283
158
|
/>
|
|
284
159
|
</UFormField>
|
|
285
160
|
|
|
286
|
-
<!--
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
161
|
+
<!-- Find an issue and create a task from it. Same picker the add-task form
|
|
162
|
+
uses for context issues: search by title, pick an already-imported one,
|
|
163
|
+
or paste a URL/key — choosing one opens the prefilled add-task form. The
|
|
164
|
+
search is scoped to the chosen container's repo (so a GitHub search stays
|
|
165
|
+
in that service's repo and a pasted URL / bare number resolves there), and
|
|
166
|
+
the source selector is always shown so it's clear which tracker is in use. -->
|
|
167
|
+
<UFormField :label="t('tasks.import.searchIssues')">
|
|
168
|
+
<ContextIssuePicker
|
|
169
|
+
v-model:source="source"
|
|
170
|
+
:scope-block-id="containerId"
|
|
171
|
+
always-show-source
|
|
172
|
+
@pick="createFromPick"
|
|
295
173
|
/>
|
|
296
174
|
</UFormField>
|
|
297
175
|
|
|
298
|
-
<!--
|
|
299
|
-
<
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
<!-- Search results (not yet imported): click a hit to create a task from it
|
|
319
|
-
(opens the prefilled add-task form); the icon button views it on GitHub. -->
|
|
320
|
-
<div v-if="searchError" class="text-[11px] text-amber-400">
|
|
321
|
-
{{ t('tasks.import.searchFailed', { error: searchError }) }}
|
|
322
|
-
</div>
|
|
323
|
-
<div v-if="freshHits.length" class="space-y-2">
|
|
324
|
-
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
325
|
-
{{ t('tasks.import.searchResults') }}
|
|
326
|
-
</h3>
|
|
327
|
-
<div
|
|
328
|
-
v-for="hit in freshHits"
|
|
329
|
-
:key="`hit:${hit.source}:${hit.externalId}`"
|
|
330
|
-
class="flex items-start justify-between gap-2 rounded-lg border border-slate-800 bg-slate-900/60 p-3 transition-colors hover:border-primary-500/60 hover:bg-slate-900"
|
|
331
|
-
>
|
|
332
|
-
<button
|
|
333
|
-
type="button"
|
|
334
|
-
class="min-w-0 flex-1 text-start disabled:cursor-not-allowed disabled:opacity-60"
|
|
335
|
-
:disabled="!containerId"
|
|
176
|
+
<!-- Secondary: spawn a parent/epic issue as a whole linked task group. -->
|
|
177
|
+
<div class="space-y-2 border-t border-slate-800 pt-3">
|
|
178
|
+
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
179
|
+
{{ t('tasks.import.asEpic') }}
|
|
180
|
+
</span>
|
|
181
|
+
<div class="flex items-end gap-2">
|
|
182
|
+
<UFormField :label="descriptor?.refLabel ?? t('tasks.import.refLabel')" class="flex-1">
|
|
183
|
+
<UInput
|
|
184
|
+
v-model="ref_"
|
|
185
|
+
:placeholder="descriptor?.refPlaceholder"
|
|
186
|
+
class="w-full"
|
|
187
|
+
@keydown.enter="doSpawnEpic"
|
|
188
|
+
/>
|
|
189
|
+
</UFormField>
|
|
190
|
+
<UButton
|
|
191
|
+
color="primary"
|
|
192
|
+
variant="soft"
|
|
193
|
+
icon="i-lucide-layers"
|
|
194
|
+
:loading="importing"
|
|
195
|
+
:disabled="!ref_.trim() || !containerId"
|
|
336
196
|
:title="
|
|
337
197
|
containerId
|
|
338
|
-
? t('tasks.import.
|
|
339
|
-
: t('tasks.import.
|
|
198
|
+
? t('tasks.import.asEpicTitleReady')
|
|
199
|
+
: t('tasks.import.asEpicTitleNeedsContainer')
|
|
340
200
|
"
|
|
341
|
-
@click="
|
|
201
|
+
@click="doSpawnEpic"
|
|
342
202
|
>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
</span>
|
|
346
|
-
<span v-if="hit.excerpt" class="mt-0.5 line-clamp-2 block text-xs text-slate-500">
|
|
347
|
-
{{ hit.excerpt }}
|
|
348
|
-
</span>
|
|
349
|
-
</button>
|
|
350
|
-
<div class="flex shrink-0 items-center gap-2">
|
|
351
|
-
<UBadge v-if="hit.status" color="neutral" variant="soft" size="xs">
|
|
352
|
-
{{ hit.status }}
|
|
353
|
-
</UBadge>
|
|
354
|
-
<UButton
|
|
355
|
-
color="neutral"
|
|
356
|
-
variant="ghost"
|
|
357
|
-
size="xs"
|
|
358
|
-
icon="i-lucide-external-link"
|
|
359
|
-
:to="hit.url"
|
|
360
|
-
target="_blank"
|
|
361
|
-
rel="noopener"
|
|
362
|
-
:aria-label="t('tasks.import.viewOnGitHub', { id: hit.externalId })"
|
|
363
|
-
/>
|
|
364
|
-
</div>
|
|
203
|
+
{{ t('tasks.import.asEpic') }}
|
|
204
|
+
</UButton>
|
|
365
205
|
</div>
|
|
366
206
|
</div>
|
|
367
|
-
|
|
368
|
-
<p
|
|
369
|
-
v-if="!freshHits.length && !searchQuery.trim()"
|
|
370
|
-
class="text-center text-xs text-slate-500"
|
|
371
|
-
>
|
|
372
|
-
{{ t('tasks.import.emptyHint') }}
|
|
373
|
-
</p>
|
|
374
207
|
</div>
|
|
375
208
|
</template>
|
|
376
209
|
</UModal>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.74.
|
|
3
|
+
"version": "0.74.2",
|
|
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.81.
|
|
37
|
+
"@cat-factory/contracts": "0.81.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|