@cat-factory/app 0.74.2 → 0.75.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.
- package/app/components/fragments/FragmentLibraryManager.vue +410 -283
- package/app/components/github/GitHubRepoSearchSelect.vue +118 -0
- package/app/components/tasks/ContextIssuePicker.vue +24 -4
- package/app/composables/api/tasks.ts +5 -1
- package/app/stores/tasks.ts +13 -0
- package/i18n/locales/en.json +8 -3
- package/i18n/locales/es.json +8 -3
- package/i18n/locales/fr.json +8 -3
- package/i18n/locales/he.json +8 -3
- package/i18n/locales/ja.json +8 -3
- package/i18n/locales/pl.json +8 -3
- package/i18n/locales/tr.json +8 -3
- package/i18n/locales/uk.json +8 -3
- package/package.json +2 -2
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
// run. The account scope has no resolved/merged catalog and fetches document
|
|
8
8
|
// fragments through `viaWorkspaceId` (document-source credentials are per-workspace).
|
|
9
9
|
import { computed, ref, watch } from 'vue'
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
DocumentSourceKind,
|
|
12
|
+
FragmentOwnerKind,
|
|
13
|
+
GitHubAvailableRepo,
|
|
14
|
+
ResolvedFragment,
|
|
15
|
+
} from '~/types/domain'
|
|
11
16
|
import { useFragmentLibrary, useFragmentLibraryStore } from '~/stores/fragmentLibrary'
|
|
17
|
+
import GitHubRepoSearchSelect from '~/components/github/GitHubRepoSearchSelect.vue'
|
|
18
|
+
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
12
19
|
|
|
13
20
|
const props = withDefaults(
|
|
14
21
|
defineProps<{
|
|
@@ -29,6 +36,7 @@ const library =
|
|
|
29
36
|
? useFragmentLibraryStore()
|
|
30
37
|
: useFragmentLibrary(props.kind, props.ownerId)
|
|
31
38
|
const documents = useDocumentsStore()
|
|
39
|
+
const github = useGitHubStore()
|
|
32
40
|
const toast = useToast()
|
|
33
41
|
const { t, d } = useI18n()
|
|
34
42
|
const { confirm } = useConfirm()
|
|
@@ -50,10 +58,17 @@ watch(
|
|
|
50
58
|
() => {
|
|
51
59
|
void library.probe()
|
|
52
60
|
void documents.probe()
|
|
61
|
+
// The GitHub pickers (repo search + tree browser) need the active board's
|
|
62
|
+
// installation state; probe once so they light up when the App is connected.
|
|
63
|
+
void github.probe()
|
|
53
64
|
},
|
|
54
65
|
{ immediate: true },
|
|
55
66
|
)
|
|
56
67
|
|
|
68
|
+
// The rich GitHub pickers reuse the active board's App installation. When it isn't
|
|
69
|
+
// connected (or the integration is off) both forms fall back to manual text entry.
|
|
70
|
+
const githubReady = computed(() => github.available === true && github.connected)
|
|
71
|
+
|
|
57
72
|
type Tab = 'catalog' | 'authored' | 'documents' | 'sources'
|
|
58
73
|
const tabs = computed<Tab[]>(() =>
|
|
59
74
|
props.showCatalog
|
|
@@ -147,6 +162,40 @@ const docDraftValid = computed(
|
|
|
147
162
|
() => !docLinkDisabled.value && docDraft.value.source && docDraft.value.ref.trim(),
|
|
148
163
|
)
|
|
149
164
|
|
|
165
|
+
// ---- GitHub file picker (documents tab) -----------------------------------
|
|
166
|
+
// For a GitHub source, let the user search a repo + browse to the file instead of
|
|
167
|
+
// hand-typing a `owner/repo:path` ref. Picking a file fills `docDraft.ref` (still
|
|
168
|
+
// editable, so pasting a URL/shorthand keeps working). Only offered when the App is
|
|
169
|
+
// connected; other sources (Confluence/Notion) keep the free-text ref field.
|
|
170
|
+
const isGithubDoc = computed(() => docDraft.value.source === 'github')
|
|
171
|
+
const showGithubDocPicker = computed(() => isGithubDoc.value && githubReady.value)
|
|
172
|
+
const docRepoId = ref<number | undefined>(undefined)
|
|
173
|
+
const docRepo = ref<GitHubAvailableRepo | undefined>(undefined)
|
|
174
|
+
const docFilePath = ref<string | undefined>(undefined)
|
|
175
|
+
|
|
176
|
+
// Reset the picker (and the derived ref) whenever the selected source changes.
|
|
177
|
+
watch(
|
|
178
|
+
() => docDraft.value.source,
|
|
179
|
+
() => {
|
|
180
|
+
docRepoId.value = undefined
|
|
181
|
+
docRepo.value = undefined
|
|
182
|
+
docFilePath.value = undefined
|
|
183
|
+
docDraft.value.ref = ''
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
// A new repo selection clears the previously-browsed file.
|
|
188
|
+
watch(docRepoId, () => {
|
|
189
|
+
docFilePath.value = undefined
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Derive the canonical `owner/repo:path` ref the GitHub docs provider expects.
|
|
193
|
+
watch([docRepo, docFilePath], () => {
|
|
194
|
+
if (docRepo.value && docFilePath.value) {
|
|
195
|
+
docDraft.value.ref = `${docRepo.value.owner}/${docRepo.value.name}:${docFilePath.value}`
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
150
199
|
/** This tier's existing document-backed fragments. */
|
|
151
200
|
const documentFragments = computed(() => library.fragments.filter((f) => f.documentRef))
|
|
152
201
|
|
|
@@ -178,21 +227,50 @@ async function refreshFragment(id: string) {
|
|
|
178
227
|
}
|
|
179
228
|
|
|
180
229
|
// ---- repo sources ----------------------------------------------------------
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
230
|
+
// When the App is connected the user searches a repo + browses to the guideline
|
|
231
|
+
// directory; otherwise the manual owner/name/dir fields are the fallback.
|
|
232
|
+
const sourceRepoId = ref<number | undefined>(undefined)
|
|
233
|
+
const sourceRepo = ref<GitHubAvailableRepo | undefined>(undefined)
|
|
234
|
+
const sourceDir = ref<string | undefined>(undefined)
|
|
235
|
+
const sourceRef = ref('')
|
|
236
|
+
const manualSource = ref({ repoOwner: '', repoName: '', dirPath: '' })
|
|
237
|
+
|
|
238
|
+
// A new repo selection clears the previously-browsed directory.
|
|
239
|
+
watch(sourceRepoId, () => {
|
|
240
|
+
sourceDir.value = undefined
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const sourceOwnerName = computed<{ owner: string; name: string } | null>(() => {
|
|
244
|
+
if (githubReady.value) {
|
|
245
|
+
return sourceRepo.value ? { owner: sourceRepo.value.owner, name: sourceRepo.value.name } : null
|
|
246
|
+
}
|
|
247
|
+
const owner = manualSource.value.repoOwner.trim()
|
|
248
|
+
const name = manualSource.value.repoName.trim()
|
|
249
|
+
return owner && name ? { owner, name } : null
|
|
250
|
+
})
|
|
251
|
+
const sourceValid = computed(() => sourceOwnerName.value !== null)
|
|
252
|
+
|
|
253
|
+
function resetSourceDraft() {
|
|
254
|
+
sourceRepoId.value = undefined
|
|
255
|
+
sourceRepo.value = undefined
|
|
256
|
+
sourceDir.value = undefined
|
|
257
|
+
sourceRef.value = ''
|
|
258
|
+
manualSource.value = { repoOwner: '', repoName: '', dirPath: '' }
|
|
259
|
+
}
|
|
185
260
|
|
|
186
261
|
async function linkSource() {
|
|
187
|
-
|
|
262
|
+
const ownerName = sourceOwnerName.value
|
|
263
|
+
if (!ownerName) return
|
|
264
|
+
const dirPath =
|
|
265
|
+
(githubReady.value ? sourceDir.value : manualSource.value.dirPath.trim()) || undefined
|
|
188
266
|
try {
|
|
189
267
|
const source = await library.linkSource({
|
|
190
|
-
repoOwner:
|
|
191
|
-
repoName:
|
|
192
|
-
dirPath
|
|
193
|
-
gitRef:
|
|
268
|
+
repoOwner: ownerName.owner,
|
|
269
|
+
repoName: ownerName.name,
|
|
270
|
+
dirPath,
|
|
271
|
+
gitRef: sourceRef.value.trim() || undefined,
|
|
194
272
|
})
|
|
195
|
-
|
|
273
|
+
resetSourceDraft()
|
|
196
274
|
await library.syncSource(source.id)
|
|
197
275
|
toast.add({ title: t('fragments.toast.sourceLinked'), icon: 'i-lucide-git-branch' })
|
|
198
276
|
} catch (e) {
|
|
@@ -242,315 +320,364 @@ async function unlinkSource(id: string) {
|
|
|
242
320
|
|
|
243
321
|
<template>
|
|
244
322
|
<div class="flex flex-col gap-4">
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
</p>
|
|
253
|
-
|
|
254
|
-
<div class="flex gap-2">
|
|
255
|
-
<UButton
|
|
256
|
-
v-for="t in tabs"
|
|
257
|
-
:key="t"
|
|
258
|
-
:color="tab === t ? 'primary' : 'neutral'"
|
|
259
|
-
:variant="tab === t ? 'solid' : 'ghost'"
|
|
260
|
-
size="sm"
|
|
261
|
-
@click="tab = t"
|
|
262
|
-
>
|
|
263
|
-
{{ tabLabel(t) }}
|
|
264
|
-
</UButton>
|
|
323
|
+
<!-- The library is opt-out; if a deployment disabled it, don't offer forms that
|
|
324
|
+
would fail with a raw 503 — say so instead (any entry point lands here). -->
|
|
325
|
+
<div
|
|
326
|
+
v-if="library.available === false"
|
|
327
|
+
class="rounded-md border border-slate-800 bg-slate-900/40 p-3 text-sm text-slate-400"
|
|
328
|
+
>
|
|
329
|
+
{{ t('fragments.unavailable') }}
|
|
265
330
|
</div>
|
|
266
331
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
}}
|
|
332
|
+
<template v-else>
|
|
333
|
+
<p class="text-sm text-slate-400">
|
|
334
|
+
<template v-if="isWorkspace">
|
|
335
|
+
{{ t('fragments.intro.workspace') }}
|
|
336
|
+
</template>
|
|
337
|
+
<template v-else>
|
|
338
|
+
{{ t('fragments.intro.account') }}
|
|
339
|
+
</template>
|
|
277
340
|
</p>
|
|
278
|
-
<div
|
|
279
|
-
v-for="f in library.resolved"
|
|
280
|
-
:key="f.id"
|
|
281
|
-
class="rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
282
|
-
>
|
|
283
|
-
<div class="flex items-center gap-2">
|
|
284
|
-
<span class="font-medium text-slate-100">{{ f.title }}</span>
|
|
285
|
-
<UBadge size="xs" :color="tierColor[f.tier]" variant="subtle">
|
|
286
|
-
{{ tierLabel[f.tier] }}
|
|
287
|
-
</UBadge>
|
|
288
|
-
<UBadge
|
|
289
|
-
v-if="f.documentRef"
|
|
290
|
-
size="xs"
|
|
291
|
-
color="success"
|
|
292
|
-
variant="subtle"
|
|
293
|
-
icon="i-lucide-radio"
|
|
294
|
-
>
|
|
295
|
-
{{ t('fragments.catalog.live', { source: f.documentRef.source }) }}
|
|
296
|
-
</UBadge>
|
|
297
|
-
<span class="ms-auto font-mono text-[11px] text-slate-500">{{ f.id }}</span>
|
|
298
|
-
</div>
|
|
299
|
-
<p class="mt-1 text-sm text-slate-400">{{ f.summary }}</p>
|
|
300
|
-
<div v-if="f.tags?.length" class="mt-1 flex flex-wrap gap-1">
|
|
301
|
-
<UBadge v-for="tag in f.tags" :key="tag" size="xs" variant="outline" color="neutral">
|
|
302
|
-
{{ tag }}
|
|
303
|
-
</UBadge>
|
|
304
|
-
</div>
|
|
305
|
-
</div>
|
|
306
|
-
</div>
|
|
307
341
|
|
|
308
|
-
|
|
309
|
-
<div v-else-if="tab === 'authored'" class="flex flex-col gap-3">
|
|
310
|
-
<div
|
|
311
|
-
v-for="f in library.fragments"
|
|
312
|
-
:key="f.id"
|
|
313
|
-
class="flex items-start gap-2 rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
314
|
-
>
|
|
315
|
-
<div class="min-w-0">
|
|
316
|
-
<div class="flex items-center gap-2">
|
|
317
|
-
<span class="font-medium text-slate-100">{{ f.title }}</span>
|
|
318
|
-
<UBadge v-if="f.source" size="xs" color="info" variant="subtle">{{
|
|
319
|
-
t('fragments.authored.fromRepo')
|
|
320
|
-
}}</UBadge>
|
|
321
|
-
</div>
|
|
322
|
-
<p class="text-sm text-slate-400">{{ f.summary }}</p>
|
|
323
|
-
</div>
|
|
342
|
+
<div class="flex gap-2">
|
|
324
343
|
<UButton
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
color="
|
|
328
|
-
variant="ghost"
|
|
329
|
-
|
|
330
|
-
@click="
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
{{
|
|
335
|
-
isWorkspace
|
|
336
|
-
? t('fragments.authored.empty.workspace')
|
|
337
|
-
: t('fragments.authored.empty.account')
|
|
338
|
-
}}
|
|
339
|
-
</p>
|
|
340
|
-
|
|
341
|
-
<div class="rounded-md border border-slate-800 p-3">
|
|
342
|
-
<p class="mb-2 text-sm font-medium">{{ t('fragments.authored.addTitle') }}</p>
|
|
343
|
-
<div class="flex flex-col gap-2">
|
|
344
|
-
<UInput v-model="draft.title" :placeholder="t('fragments.authored.titlePlaceholder')" />
|
|
345
|
-
<UInput
|
|
346
|
-
v-model="draft.summary"
|
|
347
|
-
:placeholder="t('fragments.authored.summaryPlaceholder')"
|
|
348
|
-
/>
|
|
349
|
-
<UTextarea
|
|
350
|
-
v-model="draft.body"
|
|
351
|
-
:placeholder="t('fragments.authored.bodyPlaceholder')"
|
|
352
|
-
:rows="4"
|
|
353
|
-
/>
|
|
354
|
-
<UInput v-model="draft.tags" :placeholder="t('fragments.authored.tagsPlaceholder')" />
|
|
355
|
-
<UButton
|
|
356
|
-
icon="i-lucide-plus"
|
|
357
|
-
size="sm"
|
|
358
|
-
:disabled="!draftValid"
|
|
359
|
-
:loading="library.loading"
|
|
360
|
-
class="self-start"
|
|
361
|
-
@click="createFragment"
|
|
362
|
-
>
|
|
363
|
-
{{ t('fragments.authored.add') }}
|
|
364
|
-
</UButton>
|
|
365
|
-
</div>
|
|
344
|
+
v-for="t in tabs"
|
|
345
|
+
:key="t"
|
|
346
|
+
:color="tab === t ? 'primary' : 'neutral'"
|
|
347
|
+
:variant="tab === t ? 'solid' : 'ghost'"
|
|
348
|
+
size="sm"
|
|
349
|
+
@click="tab = t"
|
|
350
|
+
>
|
|
351
|
+
{{ tabLabel(t) }}
|
|
352
|
+
</UButton>
|
|
366
353
|
</div>
|
|
367
|
-
</div>
|
|
368
354
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
<
|
|
381
|
-
|
|
355
|
+
<!-- Resolved (merged) catalog — workspace scope only -->
|
|
356
|
+
<div v-if="tab === 'catalog'" class="flex flex-col gap-2">
|
|
357
|
+
<p class="text-xs text-slate-500">
|
|
358
|
+
{{
|
|
359
|
+
t(
|
|
360
|
+
'fragments.catalog.summary',
|
|
361
|
+
{ count: library.resolved.length, builtin: library.builtinCount },
|
|
362
|
+
library.resolved.length,
|
|
363
|
+
)
|
|
364
|
+
}}
|
|
365
|
+
</p>
|
|
366
|
+
<div
|
|
367
|
+
v-for="f in library.resolved"
|
|
368
|
+
:key="f.id"
|
|
369
|
+
class="rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
370
|
+
>
|
|
382
371
|
<div class="flex items-center gap-2">
|
|
383
372
|
<span class="font-medium text-slate-100">{{ f.title }}</span>
|
|
384
|
-
<UBadge size="xs" color="
|
|
385
|
-
{{ f.
|
|
373
|
+
<UBadge size="xs" :color="tierColor[f.tier]" variant="subtle">
|
|
374
|
+
{{ tierLabel[f.tier] }}
|
|
375
|
+
</UBadge>
|
|
376
|
+
<UBadge
|
|
377
|
+
v-if="f.documentRef"
|
|
378
|
+
size="xs"
|
|
379
|
+
color="success"
|
|
380
|
+
variant="subtle"
|
|
381
|
+
icon="i-lucide-radio"
|
|
382
|
+
>
|
|
383
|
+
{{ t('fragments.catalog.live', { source: f.documentRef.source }) }}
|
|
384
|
+
</UBadge>
|
|
385
|
+
<span class="ms-auto font-mono text-[11px] text-slate-500">{{ f.id }}</span>
|
|
386
|
+
</div>
|
|
387
|
+
<p class="mt-1 text-sm text-slate-400">{{ f.summary }}</p>
|
|
388
|
+
<div v-if="f.tags?.length" class="mt-1 flex flex-wrap gap-1">
|
|
389
|
+
<UBadge v-for="tag in f.tags" :key="tag" size="xs" variant="outline" color="neutral">
|
|
390
|
+
{{ tag }}
|
|
386
391
|
</UBadge>
|
|
387
392
|
</div>
|
|
388
|
-
<p class="text-sm text-slate-400">{{ f.summary }}</p>
|
|
389
|
-
<p v-if="f.resolvedAt" class="text-[11px] text-slate-500">
|
|
390
|
-
{{ t('fragments.documents.lastResolved', { date: d(new Date(f.resolvedAt), 'long') }) }}
|
|
391
|
-
</p>
|
|
392
393
|
</div>
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<!-- Hand-authored (this tier) -->
|
|
397
|
+
<div v-else-if="tab === 'authored'" class="flex flex-col gap-3">
|
|
398
|
+
<div
|
|
399
|
+
v-for="f in library.fragments"
|
|
400
|
+
:key="f.id"
|
|
401
|
+
class="flex items-start gap-2 rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
402
|
+
>
|
|
403
|
+
<div class="min-w-0">
|
|
404
|
+
<div class="flex items-center gap-2">
|
|
405
|
+
<span class="font-medium text-slate-100">{{ f.title }}</span>
|
|
406
|
+
<UBadge v-if="f.source" size="xs" color="info" variant="subtle">{{
|
|
407
|
+
t('fragments.authored.fromRepo')
|
|
408
|
+
}}</UBadge>
|
|
409
|
+
</div>
|
|
410
|
+
<p class="text-sm text-slate-400">{{ f.summary }}</p>
|
|
411
|
+
</div>
|
|
402
412
|
<UButton
|
|
403
413
|
icon="i-lucide-trash-2"
|
|
404
414
|
size="xs"
|
|
405
415
|
color="error"
|
|
406
416
|
variant="ghost"
|
|
417
|
+
class="ms-auto"
|
|
407
418
|
@click="removeFragment(f.id)"
|
|
408
419
|
/>
|
|
409
420
|
</div>
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
421
|
+
<p v-if="!library.fragments.length" class="text-sm text-slate-500">
|
|
422
|
+
{{
|
|
423
|
+
isWorkspace
|
|
424
|
+
? t('fragments.authored.empty.workspace')
|
|
425
|
+
: t('fragments.authored.empty.account')
|
|
426
|
+
}}
|
|
427
|
+
</p>
|
|
414
428
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
429
|
+
<div class="rounded-md border border-slate-800 p-3">
|
|
430
|
+
<p class="mb-2 text-sm font-medium">{{ t('fragments.authored.addTitle') }}</p>
|
|
431
|
+
<div class="flex flex-col gap-2">
|
|
432
|
+
<UInput v-model="draft.title" :placeholder="t('fragments.authored.titlePlaceholder')" />
|
|
433
|
+
<UInput
|
|
434
|
+
v-model="draft.summary"
|
|
435
|
+
:placeholder="t('fragments.authored.summaryPlaceholder')"
|
|
436
|
+
/>
|
|
437
|
+
<UTextarea
|
|
438
|
+
v-model="draft.body"
|
|
439
|
+
:placeholder="t('fragments.authored.bodyPlaceholder')"
|
|
440
|
+
:rows="4"
|
|
441
|
+
/>
|
|
442
|
+
<UInput v-model="draft.tags" :placeholder="t('fragments.authored.tagsPlaceholder')" />
|
|
425
443
|
<UButton
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
:
|
|
430
|
-
|
|
431
|
-
@click="
|
|
444
|
+
icon="i-lucide-plus"
|
|
445
|
+
size="sm"
|
|
446
|
+
:disabled="!draftValid"
|
|
447
|
+
:loading="library.loading"
|
|
448
|
+
class="self-start"
|
|
449
|
+
@click="createFragment"
|
|
432
450
|
>
|
|
433
|
-
{{
|
|
451
|
+
{{ t('fragments.authored.add') }}
|
|
434
452
|
</UButton>
|
|
435
453
|
</div>
|
|
436
|
-
<UInput v-model="docDraft.ref" :placeholder="t('fragments.documents.refPlaceholder')" />
|
|
437
|
-
<UInput v-model="docDraft.tags" :placeholder="t('fragments.documents.tagsPlaceholder')" />
|
|
438
|
-
<UButton
|
|
439
|
-
icon="i-lucide-link"
|
|
440
|
-
size="sm"
|
|
441
|
-
:disabled="!docDraftValid"
|
|
442
|
-
:loading="library.loading"
|
|
443
|
-
class="self-start"
|
|
444
|
-
@click="linkDocumentFragment"
|
|
445
|
-
>
|
|
446
|
-
{{ t('fragments.documents.link') }}
|
|
447
|
-
</UButton>
|
|
448
454
|
</div>
|
|
449
455
|
</div>
|
|
450
|
-
</div>
|
|
451
456
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
class="flex items-center gap-2 rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
458
|
-
>
|
|
459
|
-
<UIcon name="i-lucide-git-branch" class="h-4 w-4 text-slate-400" />
|
|
460
|
-
<div class="min-w-0">
|
|
461
|
-
<span class="font-mono text-sm text-slate-100">
|
|
462
|
-
{{ s.repoOwner }}/{{ s.repoName
|
|
463
|
-
}}<span class="text-slate-500">/{{ s.dirPath || '' }}</span>
|
|
464
|
-
</span>
|
|
465
|
-
<p class="text-xs text-slate-500">
|
|
466
|
-
{{
|
|
467
|
-
s.lastSyncedAt
|
|
468
|
-
? t('fragments.sources.metaSynced', { ref: s.gitRef })
|
|
469
|
-
: t('fragments.sources.metaNever', { ref: s.gitRef })
|
|
470
|
-
}}
|
|
471
|
-
</p>
|
|
472
|
-
</div>
|
|
473
|
-
<UBadge
|
|
474
|
-
v-if="library.sourceChanges[s.id]"
|
|
475
|
-
size="xs"
|
|
476
|
-
color="warning"
|
|
477
|
-
variant="subtle"
|
|
478
|
-
class="ms-auto"
|
|
479
|
-
>
|
|
480
|
-
{{
|
|
481
|
-
t(
|
|
482
|
-
'fragments.sources.changes',
|
|
483
|
-
{ count: library.sourceChanges[s.id] },
|
|
484
|
-
library.sourceChanges[s.id] ?? 0,
|
|
485
|
-
)
|
|
486
|
-
}}
|
|
487
|
-
</UBadge>
|
|
488
|
-
<div class="ms-auto flex gap-1">
|
|
489
|
-
<UButton
|
|
490
|
-
icon="i-lucide-search-check"
|
|
491
|
-
size="xs"
|
|
492
|
-
variant="ghost"
|
|
493
|
-
@click="checkSource(s.id)"
|
|
494
|
-
/>
|
|
495
|
-
<UButton
|
|
496
|
-
icon="i-lucide-refresh-cw"
|
|
497
|
-
size="xs"
|
|
498
|
-
variant="ghost"
|
|
499
|
-
:loading="library.loading"
|
|
500
|
-
@click="syncSource(s.id)"
|
|
501
|
-
/>
|
|
502
|
-
<UButton
|
|
503
|
-
icon="i-lucide-unplug"
|
|
504
|
-
size="xs"
|
|
505
|
-
color="error"
|
|
506
|
-
variant="ghost"
|
|
507
|
-
@click="unlinkSource(s.id)"
|
|
508
|
-
/>
|
|
509
|
-
</div>
|
|
510
|
-
</div>
|
|
511
|
-
<p v-if="!library.sources.length" class="text-sm text-slate-500">
|
|
512
|
-
{{ t('fragments.sources.empty') }}
|
|
513
|
-
</p>
|
|
457
|
+
<!-- Document-backed (living) fragments -->
|
|
458
|
+
<div v-else-if="tab === 'documents'" class="flex flex-col gap-3">
|
|
459
|
+
<p class="text-xs text-slate-500">
|
|
460
|
+
{{ t('fragments.documents.intro') }}
|
|
461
|
+
</p>
|
|
514
462
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
463
|
+
<div
|
|
464
|
+
v-for="f in documentFragments"
|
|
465
|
+
:key="f.id"
|
|
466
|
+
class="flex items-start gap-2 rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
467
|
+
>
|
|
468
|
+
<UIcon name="i-lucide-radio" class="mt-0.5 h-4 w-4 text-emerald-400" />
|
|
469
|
+
<div class="min-w-0">
|
|
470
|
+
<div class="flex items-center gap-2">
|
|
471
|
+
<span class="font-medium text-slate-100">{{ f.title }}</span>
|
|
472
|
+
<UBadge size="xs" color="success" variant="subtle">
|
|
473
|
+
{{ f.documentRef?.source }}
|
|
474
|
+
</UBadge>
|
|
475
|
+
</div>
|
|
476
|
+
<p class="text-sm text-slate-400">{{ f.summary }}</p>
|
|
477
|
+
<p v-if="f.resolvedAt" class="text-[11px] text-slate-500">
|
|
478
|
+
{{
|
|
479
|
+
t('fragments.documents.lastResolved', { date: d(new Date(f.resolvedAt), 'long') })
|
|
480
|
+
}}
|
|
481
|
+
</p>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="ms-auto flex gap-1">
|
|
484
|
+
<UButton
|
|
485
|
+
icon="i-lucide-refresh-cw"
|
|
486
|
+
size="xs"
|
|
487
|
+
variant="ghost"
|
|
488
|
+
:loading="library.loading"
|
|
489
|
+
:title="t('fragments.documents.refreshTitle')"
|
|
490
|
+
@click="refreshFragment(f.id)"
|
|
523
491
|
/>
|
|
524
|
-
<
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
492
|
+
<UButton
|
|
493
|
+
icon="i-lucide-trash-2"
|
|
494
|
+
size="xs"
|
|
495
|
+
color="error"
|
|
496
|
+
variant="ghost"
|
|
497
|
+
@click="removeFragment(f.id)"
|
|
528
498
|
/>
|
|
529
499
|
</div>
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
500
|
+
</div>
|
|
501
|
+
<p v-if="!documentFragments.length" class="text-sm text-slate-500">
|
|
502
|
+
{{ t('fragments.documents.empty') }}
|
|
503
|
+
</p>
|
|
504
|
+
|
|
505
|
+
<div class="rounded-md border border-slate-800 p-3">
|
|
506
|
+
<p class="mb-2 text-sm font-medium">{{ t('fragments.documents.linkTitle') }}</p>
|
|
507
|
+
<div v-if="docLinkDisabled" class="text-sm text-slate-500">
|
|
508
|
+
{{ t('fragments.documents.disabledHint') }}
|
|
509
|
+
</div>
|
|
510
|
+
<div v-else-if="!documents.connectedSources.length" class="text-sm text-slate-500">
|
|
511
|
+
{{ t('fragments.documents.connectFirst') }}
|
|
512
|
+
</div>
|
|
513
|
+
<div v-else class="flex flex-col gap-2">
|
|
514
|
+
<div class="flex flex-wrap gap-2">
|
|
515
|
+
<UButton
|
|
516
|
+
v-for="s in documents.connectedSources"
|
|
517
|
+
:key="s.source"
|
|
518
|
+
size="xs"
|
|
519
|
+
:color="docDraft.source === s.source ? 'primary' : 'neutral'"
|
|
520
|
+
:variant="docDraft.source === s.source ? 'solid' : 'outline'"
|
|
521
|
+
@click="docDraft.source = s.source"
|
|
522
|
+
>
|
|
523
|
+
{{ s.label }}
|
|
524
|
+
</UButton>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
<!-- GitHub: search a repo + browse to the file instead of typing the ref -->
|
|
528
|
+
<template v-if="showGithubDocPicker">
|
|
529
|
+
<GitHubRepoSearchSelect v-model="docRepoId" @update:repo="docRepo = $event" />
|
|
530
|
+
<div
|
|
531
|
+
v-if="docRepoId !== undefined"
|
|
532
|
+
class="rounded-md border border-slate-800 bg-slate-900/40 p-2"
|
|
533
|
+
>
|
|
534
|
+
<p class="mb-2 text-xs text-slate-400">
|
|
535
|
+
{{ t('fragments.documents.githubBrowseHint') }}
|
|
536
|
+
</p>
|
|
537
|
+
<RepoTreeBrowser v-model="docFilePath" :repo-github-id="docRepoId" mode="file" />
|
|
538
|
+
</div>
|
|
539
|
+
</template>
|
|
540
|
+
|
|
541
|
+
<UInput v-model="docDraft.ref" :placeholder="t('fragments.documents.refPlaceholder')" />
|
|
536
542
|
<UInput
|
|
537
|
-
v-model="
|
|
538
|
-
:placeholder="t('fragments.
|
|
539
|
-
class="flex-1"
|
|
543
|
+
v-model="docDraft.tags"
|
|
544
|
+
:placeholder="t('fragments.documents.tagsPlaceholder')"
|
|
540
545
|
/>
|
|
546
|
+
<UButton
|
|
547
|
+
icon="i-lucide-link"
|
|
548
|
+
size="sm"
|
|
549
|
+
:disabled="!docDraftValid"
|
|
550
|
+
:loading="library.loading"
|
|
551
|
+
class="self-start"
|
|
552
|
+
@click="linkDocumentFragment"
|
|
553
|
+
>
|
|
554
|
+
{{ t('fragments.documents.link') }}
|
|
555
|
+
</UButton>
|
|
541
556
|
</div>
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<!-- Repo sources -->
|
|
561
|
+
<div v-else class="flex flex-col gap-3">
|
|
562
|
+
<div
|
|
563
|
+
v-for="s in library.sources"
|
|
564
|
+
:key="s.id"
|
|
565
|
+
class="flex items-center gap-2 rounded-md border border-slate-800 bg-slate-900/60 p-3"
|
|
566
|
+
>
|
|
567
|
+
<UIcon name="i-lucide-git-branch" class="h-4 w-4 text-slate-400" />
|
|
568
|
+
<div class="min-w-0">
|
|
569
|
+
<span class="font-mono text-sm text-slate-100">
|
|
570
|
+
{{ s.repoOwner }}/{{ s.repoName
|
|
571
|
+
}}<span class="text-slate-500">/{{ s.dirPath || '' }}</span>
|
|
572
|
+
</span>
|
|
573
|
+
<p class="text-xs text-slate-500">
|
|
574
|
+
{{
|
|
575
|
+
s.lastSyncedAt
|
|
576
|
+
? t('fragments.sources.metaSynced', { ref: s.gitRef })
|
|
577
|
+
: t('fragments.sources.metaNever', { ref: s.gitRef })
|
|
578
|
+
}}
|
|
579
|
+
</p>
|
|
580
|
+
</div>
|
|
581
|
+
<UBadge
|
|
582
|
+
v-if="library.sourceChanges[s.id]"
|
|
583
|
+
size="xs"
|
|
584
|
+
color="warning"
|
|
585
|
+
variant="subtle"
|
|
586
|
+
class="ms-auto"
|
|
549
587
|
>
|
|
550
|
-
{{
|
|
551
|
-
|
|
588
|
+
{{
|
|
589
|
+
t(
|
|
590
|
+
'fragments.sources.changes',
|
|
591
|
+
{ count: library.sourceChanges[s.id] },
|
|
592
|
+
library.sourceChanges[s.id] ?? 0,
|
|
593
|
+
)
|
|
594
|
+
}}
|
|
595
|
+
</UBadge>
|
|
596
|
+
<div class="ms-auto flex gap-1">
|
|
597
|
+
<UButton
|
|
598
|
+
icon="i-lucide-search-check"
|
|
599
|
+
size="xs"
|
|
600
|
+
variant="ghost"
|
|
601
|
+
@click="checkSource(s.id)"
|
|
602
|
+
/>
|
|
603
|
+
<UButton
|
|
604
|
+
icon="i-lucide-refresh-cw"
|
|
605
|
+
size="xs"
|
|
606
|
+
variant="ghost"
|
|
607
|
+
:loading="library.loading"
|
|
608
|
+
@click="syncSource(s.id)"
|
|
609
|
+
/>
|
|
610
|
+
<UButton
|
|
611
|
+
icon="i-lucide-unplug"
|
|
612
|
+
size="xs"
|
|
613
|
+
color="error"
|
|
614
|
+
variant="ghost"
|
|
615
|
+
@click="unlinkSource(s.id)"
|
|
616
|
+
/>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
<p v-if="!library.sources.length" class="text-sm text-slate-500">
|
|
620
|
+
{{ t('fragments.sources.empty') }}
|
|
621
|
+
</p>
|
|
622
|
+
|
|
623
|
+
<div class="rounded-md border border-slate-800 p-3">
|
|
624
|
+
<p class="mb-2 text-sm font-medium">{{ t('fragments.sources.linkTitle') }}</p>
|
|
625
|
+
<div class="flex flex-col gap-2">
|
|
626
|
+
<!-- Connected: search a repo + browse to the guideline directory -->
|
|
627
|
+
<template v-if="githubReady">
|
|
628
|
+
<GitHubRepoSearchSelect v-model="sourceRepoId" @update:repo="sourceRepo = $event" />
|
|
629
|
+
<div
|
|
630
|
+
v-if="sourceRepoId !== undefined"
|
|
631
|
+
class="rounded-md border border-slate-800 bg-slate-900/40 p-2"
|
|
632
|
+
>
|
|
633
|
+
<p class="mb-2 text-xs text-slate-400">
|
|
634
|
+
{{ t('fragments.sources.browseHint') }}
|
|
635
|
+
</p>
|
|
636
|
+
<RepoTreeBrowser v-model="sourceDir" :repo-github-id="sourceRepoId" mode="dir" />
|
|
637
|
+
<p class="mt-2 truncate text-xs text-slate-400">
|
|
638
|
+
<template v-if="sourceDir">
|
|
639
|
+
{{ t('fragments.sources.selectedDir') }}
|
|
640
|
+
<code class="text-slate-200">{{ sourceDir }}</code>
|
|
641
|
+
</template>
|
|
642
|
+
<template v-else>{{ t('fragments.sources.wholeRepo') }}</template>
|
|
643
|
+
</p>
|
|
644
|
+
</div>
|
|
645
|
+
</template>
|
|
646
|
+
|
|
647
|
+
<!-- Not connected: manual owner/name/dir fallback -->
|
|
648
|
+
<template v-else>
|
|
649
|
+
<div class="flex gap-2">
|
|
650
|
+
<UInput
|
|
651
|
+
v-model="manualSource.repoOwner"
|
|
652
|
+
:placeholder="t('fragments.sources.ownerPlaceholder')"
|
|
653
|
+
class="flex-1"
|
|
654
|
+
/>
|
|
655
|
+
<UInput
|
|
656
|
+
v-model="manualSource.repoName"
|
|
657
|
+
:placeholder="t('fragments.sources.repoPlaceholder')"
|
|
658
|
+
class="flex-1"
|
|
659
|
+
/>
|
|
660
|
+
</div>
|
|
661
|
+
<UInput
|
|
662
|
+
v-model="manualSource.dirPath"
|
|
663
|
+
:placeholder="t('fragments.sources.dirPlaceholder')"
|
|
664
|
+
/>
|
|
665
|
+
</template>
|
|
666
|
+
|
|
667
|
+
<UInput v-model="sourceRef" :placeholder="t('fragments.sources.refPlaceholder')" />
|
|
668
|
+
<UButton
|
|
669
|
+
icon="i-lucide-link"
|
|
670
|
+
size="sm"
|
|
671
|
+
:disabled="!sourceValid"
|
|
672
|
+
:loading="library.loading"
|
|
673
|
+
class="self-start"
|
|
674
|
+
@click="linkSource"
|
|
675
|
+
>
|
|
676
|
+
{{ t('fragments.sources.link') }}
|
|
677
|
+
</UButton>
|
|
678
|
+
</div>
|
|
552
679
|
</div>
|
|
553
680
|
</div>
|
|
554
|
-
</
|
|
681
|
+
</template>
|
|
555
682
|
</div>
|
|
556
683
|
</template>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// A reusable server-side GitHub repository picker: the same searchable combobox the
|
|
3
|
+
// add-service modal uses, extracted so any window that needs to pick a repo the App
|
|
4
|
+
// can access gets identical behaviour (type ≥ MIN_SEARCH_LEN chars → the backend
|
|
5
|
+
// filters `owner/name`, nothing is prefetched). Exposes the selected repo's numeric
|
|
6
|
+
// id via `v-model`, and emits the full `GitHubAvailableRepo` (owner/name/flags) via
|
|
7
|
+
// `update:repo` for callers that need more than the id.
|
|
8
|
+
import { refDebounced } from '@vueuse/core'
|
|
9
|
+
import type { GitHubAvailableRepo } from '~/types/domain'
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
/** Selected repo GitHub numeric id, via v-model. */
|
|
13
|
+
modelValue?: number
|
|
14
|
+
}>()
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
'update:modelValue': [number | undefined]
|
|
17
|
+
'update:repo': [GitHubAvailableRepo | undefined]
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const { t } = useI18n()
|
|
21
|
+
const github = useGitHubStore()
|
|
22
|
+
|
|
23
|
+
// A wide App install (or a PAT) can expose hundreds of repos — too many to prefetch and
|
|
24
|
+
// filter client-side — so the picker searches SERVER-SIDE once the user types at least
|
|
25
|
+
// MIN_SEARCH_LEN characters (debounced). Below the gate the list stays empty.
|
|
26
|
+
const MIN_SEARCH_LEN = 3
|
|
27
|
+
const repoSearch = ref('')
|
|
28
|
+
const repoSearchDebounced = refDebounced(repoSearch, 250)
|
|
29
|
+
const repoQueryRaw = computed(() => repoSearchDebounced.value.trim())
|
|
30
|
+
const belowMinChars = computed(() => repoQueryRaw.value.length < MIN_SEARCH_LEN)
|
|
31
|
+
|
|
32
|
+
// The picked repo, captured when selected — the loaded list is volatile (a later search
|
|
33
|
+
// replaces it), so the selection can't be derived from `availableRepos` after the fact.
|
|
34
|
+
const selectedRepo = ref<GitHubAvailableRepo | undefined>(undefined)
|
|
35
|
+
|
|
36
|
+
function toRepoItem(r: GitHubAvailableRepo) {
|
|
37
|
+
const suffix = r.private ? t('github.addService.repoLabel.private') : ''
|
|
38
|
+
return { label: `${r.owner}/${r.name}${suffix}`, value: r.githubId }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const repoItems = computed(() => github.availableRepos.map(toRepoItem))
|
|
42
|
+
const queryMatches = computed(() => (belowMinChars.value ? [] : repoItems.value))
|
|
43
|
+
|
|
44
|
+
// Items fed to the combobox: the matches plus the current selection kept present, so the
|
|
45
|
+
// menu still renders the selected repo's label after a later search replaces the list.
|
|
46
|
+
const repoMenuItems = computed(() => {
|
|
47
|
+
const matches = queryMatches.value
|
|
48
|
+
if (props.modelValue === undefined) return matches
|
|
49
|
+
if (matches.some((r) => r.value === props.modelValue)) return matches
|
|
50
|
+
return selectedRepo.value ? [toRepoItem(selectedRepo.value), ...matches] : matches
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Fetch matches server-side as the debounced query changes; below the gate clear the list.
|
|
54
|
+
watch(repoQueryRaw, (q) => {
|
|
55
|
+
void github.loadAvailableRepos(q.length >= MIN_SEARCH_LEN ? q : '')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const selectedId = computed({
|
|
59
|
+
get: () => props.modelValue,
|
|
60
|
+
set: (v: number | undefined) => emit('update:modelValue', v),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// On selection, capture the picked repo (from the still-current loaded list) and surface it.
|
|
64
|
+
watch(
|
|
65
|
+
() => props.modelValue,
|
|
66
|
+
(id) => {
|
|
67
|
+
if (id === undefined) {
|
|
68
|
+
selectedRepo.value = undefined
|
|
69
|
+
emit('update:repo', undefined)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const found = github.availableRepos.find((r) => r.githubId === id)
|
|
73
|
+
if (found) {
|
|
74
|
+
selectedRepo.value = found
|
|
75
|
+
emit('update:repo', found)
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
function clear() {
|
|
81
|
+
emit('update:modelValue', undefined)
|
|
82
|
+
emit('update:repo', undefined)
|
|
83
|
+
repoSearch.value = ''
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<UInputMenu
|
|
89
|
+
v-model="selectedId"
|
|
90
|
+
v-model:search-term="repoSearch"
|
|
91
|
+
:items="repoMenuItems"
|
|
92
|
+
:ignore-filter="true"
|
|
93
|
+
value-key="value"
|
|
94
|
+
:loading="github.loadingAvailable"
|
|
95
|
+
icon="i-lucide-search"
|
|
96
|
+
:placeholder="t('github.addService.searchPlaceholder')"
|
|
97
|
+
class="w-full"
|
|
98
|
+
>
|
|
99
|
+
<template v-if="selectedId !== undefined" #trailing>
|
|
100
|
+
<UButton
|
|
101
|
+
color="neutral"
|
|
102
|
+
variant="link"
|
|
103
|
+
size="sm"
|
|
104
|
+
icon="i-lucide-x"
|
|
105
|
+
:aria-label="t('github.addService.clearSelection')"
|
|
106
|
+
@click.stop="clear"
|
|
107
|
+
/>
|
|
108
|
+
</template>
|
|
109
|
+
<template #empty>
|
|
110
|
+
<span v-if="belowMinChars">
|
|
111
|
+
{{ t('github.addService.searchMinChars', { min: MIN_SEARCH_LEN }, MIN_SEARCH_LEN) }}
|
|
112
|
+
</span>
|
|
113
|
+
<span v-else-if="!github.loadingAvailable">
|
|
114
|
+
{{ t('github.addService.noMatches', { query: repoQueryRaw }) }}
|
|
115
|
+
</span>
|
|
116
|
+
</template>
|
|
117
|
+
</UInputMenu>
|
|
118
|
+
</template>
|
|
@@ -66,6 +66,26 @@ const results = ref<TaskSearchResult[]>([])
|
|
|
66
66
|
const searching = ref(false)
|
|
67
67
|
const searchError = ref<string | null>(null)
|
|
68
68
|
|
|
69
|
+
// Already-imported issues, scoped to the target container's repo on the backend
|
|
70
|
+
// (GitHub narrows to the service's linked repo, exactly as search does; repo-less
|
|
71
|
+
// sources are unaffected). Held locally rather than read from the shared workspace
|
|
72
|
+
// list, so a task created for one service never offers issues from sibling repos.
|
|
73
|
+
const imported = ref<SourceTask[]>([])
|
|
74
|
+
async function reloadImported() {
|
|
75
|
+
try {
|
|
76
|
+
imported.value = await tasks.listTasksForBlock(props.scopeBlockId)
|
|
77
|
+
} catch {
|
|
78
|
+
imported.value = []
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Re-scope when the target container changes (its repo, hence the in-repo issues, differ).
|
|
82
|
+
watch(
|
|
83
|
+
() => props.scopeBlockId,
|
|
84
|
+
() => {
|
|
85
|
+
reloadImported()
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
69
89
|
// Debounced search: free text hits the tracker; a query that's clearly a URL/key
|
|
70
90
|
// is left to the explicit "by reference" row below (search won't surface it).
|
|
71
91
|
// Re-scope when `scopeBlockId` changes too (a GitHub search is scoped to the block's
|
|
@@ -108,7 +128,7 @@ function keyFor(externalId: string): string {
|
|
|
108
128
|
const importedRows = computed(() => {
|
|
109
129
|
if (!source.value) return []
|
|
110
130
|
const q = query.value.trim().toLowerCase()
|
|
111
|
-
return
|
|
131
|
+
return imported.value
|
|
112
132
|
.filter((t) => t.source === source.value)
|
|
113
133
|
.filter((t) => !chosen.value.has(keyFor(t.externalId)))
|
|
114
134
|
.filter(
|
|
@@ -120,7 +140,7 @@ const importedRows = computed(() => {
|
|
|
120
140
|
const searchRows = computed(() => {
|
|
121
141
|
if (!source.value) return []
|
|
122
142
|
const importedIds = new Set(
|
|
123
|
-
|
|
143
|
+
imported.value.filter((t) => t.source === source.value).map((t) => t.externalId),
|
|
124
144
|
)
|
|
125
145
|
return results.value
|
|
126
146
|
.filter((r) => !importedIds.has(r.externalId))
|
|
@@ -195,8 +215,8 @@ function pickRef(q: string) {
|
|
|
195
215
|
}
|
|
196
216
|
|
|
197
217
|
onMounted(() => {
|
|
198
|
-
//
|
|
199
|
-
|
|
218
|
+
// Load the quick-pick list, scoped to the target container's repo.
|
|
219
|
+
reloadImported()
|
|
200
220
|
})
|
|
201
221
|
</script>
|
|
202
222
|
|
|
@@ -62,7 +62,11 @@ export function tasksApi({ send, ws }: ApiContext) {
|
|
|
62
62
|
checkTaskSource: (workspaceId: string, source: TaskSourceKind) =>
|
|
63
63
|
send(diagnoseTaskSourceContract, { pathPrefix: ws(workspaceId), pathParams: { source } }),
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// `blockId` scopes the listed issues to that block's service repo for a
|
|
66
|
+
// repo-backed source (GitHub Issues), exactly as search does; omitted → the
|
|
67
|
+
// whole workspace.
|
|
68
|
+
listTasks: (workspaceId: string, blockId?: string) =>
|
|
69
|
+
send(listTasksContract, { pathPrefix: ws(workspaceId), queryParams: { blockId } }),
|
|
66
70
|
|
|
67
71
|
importTask: (workspaceId: string, source: TaskSourceKind, body: { ref: string }) =>
|
|
68
72
|
send(importTaskContract, { pathPrefix: ws(workspaceId), pathParams: { source }, body }),
|
package/app/stores/tasks.ts
CHANGED
|
@@ -119,6 +119,18 @@ export const useTasksStore = defineStore('tasks', () => {
|
|
|
119
119
|
tasks.value = await api.listTasks(workspace.requireId())
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Fetch imported issues scoped to a block's service repo (GitHub only — a
|
|
124
|
+
* repo-backed source narrows to that service's linked repo, exactly as `search`
|
|
125
|
+
* does; repo-less sources are unaffected). Returns the list WITHOUT touching the
|
|
126
|
+
* shared `tasks` state, so a repo-scoped view (the issue picker) can hold its own
|
|
127
|
+
* list without narrowing the workspace-wide one other views rely on. Omit
|
|
128
|
+
* `blockId` for the whole workspace.
|
|
129
|
+
*/
|
|
130
|
+
async function listTasksForBlock(blockId?: string): Promise<SourceTask[]> {
|
|
131
|
+
return api.listTasks(workspace.requireId(), blockId)
|
|
132
|
+
}
|
|
133
|
+
|
|
122
134
|
/** Import (fetch + persist) an issue by key or URL from a source. */
|
|
123
135
|
async function importTask(source: TaskSourceKind, ref: string): Promise<SourceTask> {
|
|
124
136
|
loading.value = true
|
|
@@ -218,6 +230,7 @@ export const useTasksStore = defineStore('tasks', () => {
|
|
|
218
230
|
disconnect,
|
|
219
231
|
setEnabled,
|
|
220
232
|
loadTasks,
|
|
233
|
+
listTasksForBlock,
|
|
221
234
|
importTask,
|
|
222
235
|
search,
|
|
223
236
|
linkToBlock,
|
package/i18n/locales/en.json
CHANGED
|
@@ -3717,7 +3717,8 @@
|
|
|
3717
3717
|
"connectFirst": "Connect a document source (Confluence, Notion or GitHub) under Integrations first.",
|
|
3718
3718
|
"refPlaceholder": "Page id or URL (e.g. a Confluence/Notion page or GitHub file URL)",
|
|
3719
3719
|
"tagsPlaceholder": "Tags, comma-separated (optional)",
|
|
3720
|
-
"link": "Link as living fragment"
|
|
3720
|
+
"link": "Link as living fragment",
|
|
3721
|
+
"githubBrowseHint": "Browse the repo and pick the file to link."
|
|
3721
3722
|
},
|
|
3722
3723
|
"sources": {
|
|
3723
3724
|
"metaSynced": "synced · ref {ref}",
|
|
@@ -3729,7 +3730,10 @@
|
|
|
3729
3730
|
"repoPlaceholder": "repo",
|
|
3730
3731
|
"dirPlaceholder": "dir path (e.g. guidelines)",
|
|
3731
3732
|
"refPlaceholder": "ref (default HEAD)",
|
|
3732
|
-
"link": "Link & sync"
|
|
3733
|
+
"link": "Link & sync",
|
|
3734
|
+
"browseHint": "Browse the repo and pick the directory of Markdown guidelines (or link the whole repo).",
|
|
3735
|
+
"selectedDir": "Directory:",
|
|
3736
|
+
"wholeRepo": "Whole repository (root)."
|
|
3733
3737
|
},
|
|
3734
3738
|
"toast": {
|
|
3735
3739
|
"added": "Fragment added",
|
|
@@ -3753,7 +3757,8 @@
|
|
|
3753
3757
|
"confirmRemove": {
|
|
3754
3758
|
"title": "Delete this fragment?",
|
|
3755
3759
|
"body": "\"{name}\" will be removed. This can't be undone."
|
|
3756
|
-
}
|
|
3760
|
+
},
|
|
3761
|
+
"unavailable": "The prompt-fragment library isn't enabled for this deployment."
|
|
3757
3762
|
},
|
|
3758
3763
|
"sandbox": {
|
|
3759
3764
|
"title": "Sandbox: prompt and model testing",
|
package/i18n/locales/es.json
CHANGED
|
@@ -3585,7 +3585,8 @@
|
|
|
3585
3585
|
"connectFirst": "Conecta primero una fuente de documentos (Confluence, Notion o GitHub) en Integraciones.",
|
|
3586
3586
|
"refPlaceholder": "Id o URL de página (p. ej., una página de Confluence/Notion o la URL de un archivo de GitHub)",
|
|
3587
3587
|
"tagsPlaceholder": "Etiquetas, separadas por comas (opcional)",
|
|
3588
|
-
"link": "Vincular como fragmento vivo"
|
|
3588
|
+
"link": "Vincular como fragmento vivo",
|
|
3589
|
+
"githubBrowseHint": "Explora el repositorio y elige el archivo a enlazar."
|
|
3589
3590
|
},
|
|
3590
3591
|
"sources": {
|
|
3591
3592
|
"metaSynced": "sincronizado · ref {ref}",
|
|
@@ -3597,7 +3598,10 @@
|
|
|
3597
3598
|
"repoPlaceholder": "repositorio",
|
|
3598
3599
|
"dirPlaceholder": "ruta del directorio (p. ej., guidelines)",
|
|
3599
3600
|
"refPlaceholder": "ref (HEAD por defecto)",
|
|
3600
|
-
"link": "Vincular y sincronizar"
|
|
3601
|
+
"link": "Vincular y sincronizar",
|
|
3602
|
+
"browseHint": "Explora el repositorio y elige el directorio de guías Markdown (o enlaza todo el repositorio).",
|
|
3603
|
+
"selectedDir": "Directorio:",
|
|
3604
|
+
"wholeRepo": "Repositorio completo (raíz)."
|
|
3601
3605
|
},
|
|
3602
3606
|
"toast": {
|
|
3603
3607
|
"added": "Fragmento añadido",
|
|
@@ -3621,7 +3625,8 @@
|
|
|
3621
3625
|
"confirmRemove": {
|
|
3622
3626
|
"title": "¿Eliminar este fragmento?",
|
|
3623
3627
|
"body": "Se eliminará \"{name}\". Esta acción no se puede deshacer."
|
|
3624
|
-
}
|
|
3628
|
+
},
|
|
3629
|
+
"unavailable": "La biblioteca de fragmentos de prompt no está habilitada en esta implementación."
|
|
3625
3630
|
},
|
|
3626
3631
|
"sandbox": {
|
|
3627
3632
|
"title": "Sandbox: pruebas de prompts y modelos",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -3585,7 +3585,8 @@
|
|
|
3585
3585
|
"connectFirst": "Connectez d'abord une source de documents (Confluence, Notion ou GitHub) dans Intégrations.",
|
|
3586
3586
|
"refPlaceholder": "Id ou URL de page (par ex. une page Confluence/Notion ou l'URL d'un fichier GitHub)",
|
|
3587
3587
|
"tagsPlaceholder": "Étiquettes, séparées par des virgules (facultatif)",
|
|
3588
|
-
"link": "Lier comme fragment vivant"
|
|
3588
|
+
"link": "Lier comme fragment vivant",
|
|
3589
|
+
"githubBrowseHint": "Parcourez le dépôt et choisissez le fichier à lier."
|
|
3589
3590
|
},
|
|
3590
3591
|
"sources": {
|
|
3591
3592
|
"metaSynced": "synchronisé · ref {ref}",
|
|
@@ -3597,7 +3598,10 @@
|
|
|
3597
3598
|
"repoPlaceholder": "dépôt",
|
|
3598
3599
|
"dirPlaceholder": "chemin du répertoire (par ex. guidelines)",
|
|
3599
3600
|
"refPlaceholder": "ref (HEAD par défaut)",
|
|
3600
|
-
"link": "Lier et synchroniser"
|
|
3601
|
+
"link": "Lier et synchroniser",
|
|
3602
|
+
"browseHint": "Parcourez le dépôt et choisissez le répertoire de consignes Markdown (ou liez tout le dépôt).",
|
|
3603
|
+
"selectedDir": "Répertoire :",
|
|
3604
|
+
"wholeRepo": "Dépôt entier (racine)."
|
|
3601
3605
|
},
|
|
3602
3606
|
"toast": {
|
|
3603
3607
|
"added": "Fragment ajouté",
|
|
@@ -3621,7 +3625,8 @@
|
|
|
3621
3625
|
"confirmRemove": {
|
|
3622
3626
|
"title": "Supprimer ce fragment ?",
|
|
3623
3627
|
"body": "\"{name}\" sera supprimé. Cette action est irréversible."
|
|
3624
|
-
}
|
|
3628
|
+
},
|
|
3629
|
+
"unavailable": "La bibliothèque de fragments de prompt n'est pas activée pour ce déploiement."
|
|
3625
3630
|
},
|
|
3626
3631
|
"sandbox": {
|
|
3627
3632
|
"title": "Bac à sable : test de prompts et de modèles",
|
package/i18n/locales/he.json
CHANGED
|
@@ -3596,7 +3596,8 @@
|
|
|
3596
3596
|
"connectFirst": "חבר תחילה מקור מסמכים (Confluence, Notion או GitHub) תחת אינטגרציות.",
|
|
3597
3597
|
"refPlaceholder": "מזהה עמוד או כתובת (למשל עמוד Confluence/Notion או כתובת קובץ GitHub)",
|
|
3598
3598
|
"tagsPlaceholder": "תגיות, מופרדות בפסיקים (אופציונלי)",
|
|
3599
|
-
"link": "קשר כמקטע חי"
|
|
3599
|
+
"link": "קשר כמקטע חי",
|
|
3600
|
+
"githubBrowseHint": "עיין במאגר ובחר את הקובץ לקישור."
|
|
3600
3601
|
},
|
|
3601
3602
|
"sources": {
|
|
3602
3603
|
"metaSynced": "סונכרן · ref {ref}",
|
|
@@ -3608,7 +3609,10 @@
|
|
|
3608
3609
|
"repoPlaceholder": "מאגר",
|
|
3609
3610
|
"dirPlaceholder": "נתיב תיקייה (למשל guidelines)",
|
|
3610
3611
|
"refPlaceholder": "ref (ברירת מחדל HEAD)",
|
|
3611
|
-
"link": "קשר וסנכרן"
|
|
3612
|
+
"link": "קשר וסנכרן",
|
|
3613
|
+
"browseHint": "עיין במאגר ובחר את תיקיית הנחיות ה-Markdown (או קשר את המאגר כולו).",
|
|
3614
|
+
"selectedDir": "תיקייה:",
|
|
3615
|
+
"wholeRepo": "המאגר כולו (השורש)."
|
|
3612
3616
|
},
|
|
3613
3617
|
"toast": {
|
|
3614
3618
|
"added": "המקטע נוסף",
|
|
@@ -3632,7 +3636,8 @@
|
|
|
3632
3636
|
"confirmRemove": {
|
|
3633
3637
|
"title": "למחוק את המקטע הזה?",
|
|
3634
3638
|
"body": "\"{name}\" יימחק. לא ניתן לבטל פעולה זו."
|
|
3635
|
-
}
|
|
3639
|
+
},
|
|
3640
|
+
"unavailable": "ספריית מקטעי הפרומפט אינה מופעלת בפריסה זו."
|
|
3636
3641
|
},
|
|
3637
3642
|
"sandbox": {
|
|
3638
3643
|
"title": "Sandbox: בדיקת פרומפטים ומודלים",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -3598,7 +3598,8 @@
|
|
|
3598
3598
|
"connectFirst": "まず Integrations でドキュメントソース (Confluence、Notion または GitHub) を接続してください。",
|
|
3599
3599
|
"refPlaceholder": "ページ id または URL (例: Confluence/Notion ページや GitHub ファイル URL)",
|
|
3600
3600
|
"tagsPlaceholder": "タグ、カンマ区切り (任意)",
|
|
3601
|
-
"link": "リビングフラグメントとしてリンク"
|
|
3601
|
+
"link": "リビングフラグメントとしてリンク",
|
|
3602
|
+
"githubBrowseHint": "リポジトリを参照してリンクするファイルを選択します。"
|
|
3602
3603
|
},
|
|
3603
3604
|
"sources": {
|
|
3604
3605
|
"metaSynced": "同期済み · ref {ref}",
|
|
@@ -3610,7 +3611,10 @@
|
|
|
3610
3611
|
"repoPlaceholder": "repo",
|
|
3611
3612
|
"dirPlaceholder": "ディレクトリパス (例: guidelines)",
|
|
3612
3613
|
"refPlaceholder": "ref (デフォルト HEAD)",
|
|
3613
|
-
"link": "リンクして同期"
|
|
3614
|
+
"link": "リンクして同期",
|
|
3615
|
+
"browseHint": "リポジトリを参照して Markdown ガイドラインのディレクトリを選択します(またはリポジトリ全体をリンク)。",
|
|
3616
|
+
"selectedDir": "ディレクトリ:",
|
|
3617
|
+
"wholeRepo": "リポジトリ全体(ルート)。"
|
|
3614
3618
|
},
|
|
3615
3619
|
"toast": {
|
|
3616
3620
|
"added": "フラグメントを追加しました",
|
|
@@ -3634,7 +3638,8 @@
|
|
|
3634
3638
|
"confirmRemove": {
|
|
3635
3639
|
"title": "このフラグメントを削除しますか?",
|
|
3636
3640
|
"body": "「{name}」が削除されます。 この操作は取り消せません。"
|
|
3637
|
-
}
|
|
3641
|
+
},
|
|
3642
|
+
"unavailable": "このデプロイではプロンプトフラグメントライブラリが有効になっていません。"
|
|
3638
3643
|
},
|
|
3639
3644
|
"sandbox": {
|
|
3640
3645
|
"title": "Sandbox: プロンプトとモデルのテスト",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -3585,7 +3585,8 @@
|
|
|
3585
3585
|
"connectFirst": "Najpierw połącz źródło dokumentów (Confluence, Notion lub GitHub) w sekcji Integracje.",
|
|
3586
3586
|
"refPlaceholder": "Id lub URL strony (np. strona Confluence/Notion lub URL pliku GitHub)",
|
|
3587
3587
|
"tagsPlaceholder": "Tagi, oddzielone przecinkami (opcjonalnie)",
|
|
3588
|
-
"link": "Połącz jako żywy fragment"
|
|
3588
|
+
"link": "Połącz jako żywy fragment",
|
|
3589
|
+
"githubBrowseHint": "Przeglądaj repozytorium i wybierz plik do połączenia."
|
|
3589
3590
|
},
|
|
3590
3591
|
"sources": {
|
|
3591
3592
|
"metaSynced": "zsynchronizowano · ref {ref}",
|
|
@@ -3597,7 +3598,10 @@
|
|
|
3597
3598
|
"repoPlaceholder": "repozytorium",
|
|
3598
3599
|
"dirPlaceholder": "ścieżka katalogu (np. guidelines)",
|
|
3599
3600
|
"refPlaceholder": "ref (domyślnie HEAD)",
|
|
3600
|
-
"link": "Połącz i synchronizuj"
|
|
3601
|
+
"link": "Połącz i synchronizuj",
|
|
3602
|
+
"browseHint": "Przeglądaj repozytorium i wybierz katalog wytycznych Markdown (lub połącz całe repozytorium).",
|
|
3603
|
+
"selectedDir": "Katalog:",
|
|
3604
|
+
"wholeRepo": "Całe repozytorium (katalog główny)."
|
|
3601
3605
|
},
|
|
3602
3606
|
"toast": {
|
|
3603
3607
|
"added": "Dodano fragment",
|
|
@@ -3621,7 +3625,8 @@
|
|
|
3621
3625
|
"confirmRemove": {
|
|
3622
3626
|
"title": "Usunąć ten fragment?",
|
|
3623
3627
|
"body": "\"{name}\" zostanie usunięty. Tej operacji nie można cofnąć."
|
|
3624
|
-
}
|
|
3628
|
+
},
|
|
3629
|
+
"unavailable": "Biblioteka fragmentów promptów nie jest włączona w tym wdrożeniu."
|
|
3625
3630
|
},
|
|
3626
3631
|
"sandbox": {
|
|
3627
3632
|
"title": "Piaskownica: testowanie promptów i modeli",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -3598,7 +3598,8 @@
|
|
|
3598
3598
|
"connectFirst": "Önce Entegrasyonlar altında bir belge kaynağı (Confluence, Notion veya GitHub) bağlayın.",
|
|
3599
3599
|
"refPlaceholder": "Sayfa kimliği veya URL (örn. bir Confluence/Notion sayfası veya GitHub dosya URL'si)",
|
|
3600
3600
|
"tagsPlaceholder": "Etiketler, virgülle ayrılmış (isteğe bağlı)",
|
|
3601
|
-
"link": "Canlı parça olarak bağla"
|
|
3601
|
+
"link": "Canlı parça olarak bağla",
|
|
3602
|
+
"githubBrowseHint": "Depoya göz atın ve bağlanacak dosyayı seçin."
|
|
3602
3603
|
},
|
|
3603
3604
|
"sources": {
|
|
3604
3605
|
"metaSynced": "eşitlendi · ref {ref}",
|
|
@@ -3610,7 +3611,10 @@
|
|
|
3610
3611
|
"repoPlaceholder": "repo",
|
|
3611
3612
|
"dirPlaceholder": "dizin yolu (örn. guidelines)",
|
|
3612
3613
|
"refPlaceholder": "ref (varsayılan HEAD)",
|
|
3613
|
-
"link": "Bağla ve eşitle"
|
|
3614
|
+
"link": "Bağla ve eşitle",
|
|
3615
|
+
"browseHint": "Depoya göz atın ve Markdown yönerge dizinini seçin (veya tüm depoyu bağlayın).",
|
|
3616
|
+
"selectedDir": "Dizin:",
|
|
3617
|
+
"wholeRepo": "Tüm depo (kök)."
|
|
3614
3618
|
},
|
|
3615
3619
|
"toast": {
|
|
3616
3620
|
"added": "Parça eklendi",
|
|
@@ -3634,7 +3638,8 @@
|
|
|
3634
3638
|
"confirmRemove": {
|
|
3635
3639
|
"title": "Bu parça silinsin mi?",
|
|
3636
3640
|
"body": "\"{name}\" kaldırılacak. Bu işlem geri alınamaz."
|
|
3637
|
-
}
|
|
3641
|
+
},
|
|
3642
|
+
"unavailable": "Bu dağıtımda istem parçası kütüphanesi etkin değil."
|
|
3638
3643
|
},
|
|
3639
3644
|
"sandbox": {
|
|
3640
3645
|
"title": "Sandbox: prompt ve model testi",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -3585,7 +3585,8 @@
|
|
|
3585
3585
|
"connectFirst": "Спершу під'єднайте джерело документів (Confluence, Notion або GitHub) у розділі Інтеграції.",
|
|
3586
3586
|
"refPlaceholder": "Id або URL сторінки (напр. сторінка Confluence/Notion чи URL файлу GitHub)",
|
|
3587
3587
|
"tagsPlaceholder": "Теги, розділені комами (необов'язково)",
|
|
3588
|
-
"link": "Прив'язати як живий фрагмент"
|
|
3588
|
+
"link": "Прив'язати як живий фрагмент",
|
|
3589
|
+
"githubBrowseHint": "Перегляньте репозиторій і виберіть файл для зв’язування."
|
|
3589
3590
|
},
|
|
3590
3591
|
"sources": {
|
|
3591
3592
|
"metaSynced": "синхронізовано · ref {ref}",
|
|
@@ -3597,7 +3598,10 @@
|
|
|
3597
3598
|
"repoPlaceholder": "репозиторій",
|
|
3598
3599
|
"dirPlaceholder": "шлях до каталогу (напр. guidelines)",
|
|
3599
3600
|
"refPlaceholder": "ref (типово HEAD)",
|
|
3600
|
-
"link": "Прив'язати й синхронізувати"
|
|
3601
|
+
"link": "Прив'язати й синхронізувати",
|
|
3602
|
+
"browseHint": "Перегляньте репозиторій і виберіть каталог настанов Markdown (або зв’яжіть увесь репозиторій).",
|
|
3603
|
+
"selectedDir": "Каталог:",
|
|
3604
|
+
"wholeRepo": "Увесь репозиторій (корінь)."
|
|
3601
3605
|
},
|
|
3602
3606
|
"toast": {
|
|
3603
3607
|
"added": "Фрагмент додано",
|
|
@@ -3621,7 +3625,8 @@
|
|
|
3621
3625
|
"confirmRemove": {
|
|
3622
3626
|
"title": "Видалити цей фрагмент?",
|
|
3623
3627
|
"body": "\"{name}\" буде видалено. Цю дію не можна скасувати."
|
|
3624
|
-
}
|
|
3628
|
+
},
|
|
3629
|
+
"unavailable": "Бібліотека фрагментів промптів не ввімкнена для цього розгортання."
|
|
3625
3630
|
},
|
|
3626
3631
|
"sandbox": {
|
|
3627
3632
|
"title": "Пісочниця: тестування промптів і моделей",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.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.81.
|
|
37
|
+
"@cat-factory/contracts": "0.81.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|