@cat-factory/app 0.171.0 → 0.173.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/README.md +4 -0
- package/app/components/board/AddTaskModal.vue +10 -252
- package/app/components/board/CreateInitiativeModal.vue +27 -0
- package/app/components/board/nodes/InitiativeCard.vue +14 -0
- package/app/components/common/InterviewGateNotice.vue +39 -0
- package/app/components/context/ContextAttachmentFields.vue +289 -0
- package/app/components/docs/DocInterviewWindow.vue +77 -14
- package/app/components/fragments/FragmentLibraryManager.vue +91 -13
- package/app/components/fragments/GitHubDocUrlImport.vue +83 -0
- package/app/components/github/RepoTreeBrowser.vue +65 -10
- package/app/components/initiative/InitiativePlanningWindow.vue +82 -13
- package/app/components/layout/DefaultTestEnvBanner.vue +117 -0
- package/app/components/panels/inspector/InitiativeInspector.vue +15 -0
- package/app/components/settings/DefaultProvisionTypeSection.vue +176 -0
- package/app/components/settings/InfrastructureWindow.vue +8 -1
- package/app/composables/useContextLinking.ts +14 -2
- package/app/composables/useInitiativePlanning.ts +28 -6
- package/app/pages/index.vue +12 -1
- package/app/stores/ui/modals.ts +46 -0
- package/app/stores/workspaceSettings.ts +5 -0
- package/app/utils/defaultProvisioning.spec.ts +100 -0
- package/app/utils/defaultProvisioning.ts +99 -0
- package/app/utils/interviewGate.spec.ts +51 -0
- package/app/utils/interviewGate.ts +51 -0
- package/i18n/locales/de.json +70 -22
- package/i18n/locales/en.json +73 -22
- package/i18n/locales/es.json +70 -22
- package/i18n/locales/fr.json +70 -22
- package/i18n/locales/he.json +70 -22
- package/i18n/locales/it.json +70 -22
- package/i18n/locales/ja.json +70 -22
- package/i18n/locales/pl.json +70 -22
- package/i18n/locales/tr.json +70 -22
- package/i18n/locales/uk.json +70 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -133,6 +133,10 @@ example ships in [`deploy/frontend`](../../deploy/frontend) (the `acme:security`
|
|
|
133
133
|
single way a pipeline is chosen anywhere — add-task, run settings, the recurring
|
|
134
134
|
schedule, the focus view's Run menu — so every surface explains a pipeline by the
|
|
135
135
|
ordered steps it will run rather than by its name alone.
|
|
136
|
+
- **Context attachments** (`components/context`) — `ContextAttachmentFields`, the
|
|
137
|
+
shared staged-attachment form used by both the add-task and create-initiative
|
|
138
|
+
modals. Picks are held locally and import-and-linked once the block exists (see
|
|
139
|
+
`composables/useContextLinking`), because linking needs a block id.
|
|
136
140
|
- **Integrations** — modals/panels for `github` (the source-control panel, shared
|
|
137
141
|
by every VCS provider), `vcs` (the GitLab personal-access-token connect),
|
|
138
142
|
`bootstrap`, `documents`, `tasks`, `requirements` (review), `scenarios`
|
|
@@ -24,8 +24,7 @@ import { DOC_KINDS, DOC_KIND_FIELDS } from '~/types/domain'
|
|
|
24
24
|
import { resolveComponentRegistry } from '@modular-vue/core'
|
|
25
25
|
import { useReactiveSlots } from '@modular-vue/runtime'
|
|
26
26
|
import type { AppSlots, ResultViewContribution } from '~/modular/slots'
|
|
27
|
-
import
|
|
28
|
-
import ContextIssuePicker from '~/components/tasks/ContextIssuePicker.vue'
|
|
27
|
+
import ContextAttachmentFields from '~/components/context/ContextAttachmentFields.vue'
|
|
29
28
|
import FragmentSelector from '~/components/fragments/FragmentSelector.vue'
|
|
30
29
|
import RiskPolicyPicker from '~/components/riskPolicy/RiskPolicyPicker.vue'
|
|
31
30
|
import { parseConflict } from '~/composables/usePipelineErrorToast'
|
|
@@ -441,40 +440,6 @@ function setConfig(id: string, value: string) {
|
|
|
441
440
|
// import flow), committed once the block exists (see add() → linkPending).
|
|
442
441
|
const pendingContext = ref<PendingContext[]>([])
|
|
443
442
|
|
|
444
|
-
// The Context documents / Context issues sections mirror the task inspector but are
|
|
445
|
-
// always shown (ungated): when the relevant integration isn't connected the Attach
|
|
446
|
-
// button becomes a "Connect a source" action instead. Connecting opens the source's
|
|
447
|
-
// connect modal OVER this one (both are root-mounted with independent open flags), so
|
|
448
|
-
// the user's in-progress task data is preserved rather than lost to a navigation away.
|
|
449
|
-
const docsConnected = computed(() => documents.available && documents.anyConnected)
|
|
450
|
-
const issuesConnected = computed(() => tasks.available && tasks.anyOffered)
|
|
451
|
-
|
|
452
|
-
// Sources the user could connect right now to unlock the picker, when none is connected
|
|
453
|
-
// yet: for documents, every configured source without a live connection (GitHub docs are
|
|
454
|
-
// already implicitly connected via the App, so they never appear here); for issues, every
|
|
455
|
-
// configured tracker not yet available (a connected credentialed source, or the installed
|
|
456
|
-
// GitHub App). The connect modals are the same ones the Integrations hub opens.
|
|
457
|
-
const connectableDocSources = computed(() =>
|
|
458
|
-
documents.available ? documents.sources.filter((s) => !documents.isConnected(s.source)) : [],
|
|
459
|
-
)
|
|
460
|
-
const connectableIssueSources = computed(() =>
|
|
461
|
-
tasks.available ? tasks.sources.filter((s) => !s.available) : [],
|
|
462
|
-
)
|
|
463
|
-
const connectDocMenu = computed(() => [
|
|
464
|
-
connectableDocSources.value.map((s) => ({
|
|
465
|
-
label: s.label,
|
|
466
|
-
icon: s.icon,
|
|
467
|
-
onSelect: () => ui.openDocumentConnect(s.source),
|
|
468
|
-
})),
|
|
469
|
-
])
|
|
470
|
-
const connectIssueMenu = computed(() => [
|
|
471
|
-
connectableIssueSources.value.map((s) => ({
|
|
472
|
-
label: s.label,
|
|
473
|
-
icon: s.icon,
|
|
474
|
-
onSelect: () => ui.openTaskConnect(s.source),
|
|
475
|
-
})),
|
|
476
|
-
])
|
|
477
|
-
const pendingDocs = computed(() => pendingContext.value.filter((c) => c.kind === 'document'))
|
|
478
443
|
const pendingIssues = computed(() => pendingContext.value.filter((c) => c.kind === 'task'))
|
|
479
444
|
|
|
480
445
|
// Linked issues whose body is in hand, surfaced read-only above the description so the
|
|
@@ -532,24 +497,6 @@ async function resolvePendingIssueBodies() {
|
|
|
532
497
|
}
|
|
533
498
|
}
|
|
534
499
|
|
|
535
|
-
function addPending(item: PendingContext) {
|
|
536
|
-
if (pendingContext.value.some((c) => contextKey(c) === contextKey(item))) return
|
|
537
|
-
pendingContext.value = [...pendingContext.value, item]
|
|
538
|
-
}
|
|
539
|
-
function removePending(item: PendingContext) {
|
|
540
|
-
pendingContext.value = pendingContext.value.filter((c) => contextKey(c) !== contextKey(item))
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Context documents and issues are both picked through an inline search picker
|
|
544
|
-
// (ContextDocumentPicker / ContextIssuePicker) rather than a dropdown that opens a
|
|
545
|
-
// second modal — stacked page-level modals don't interact here, which is why the
|
|
546
|
-
// old "Import a page…" / "Import an issue…" entries appeared to open something but
|
|
547
|
-
// nothing was clickable. The "Attach" button toggles the relevant picker open.
|
|
548
|
-
const showDocPicker = ref(false)
|
|
549
|
-
const chosenDocKeys = computed(() => pendingDocs.value.map(contextKey))
|
|
550
|
-
const showIssuePicker = ref(false)
|
|
551
|
-
const chosenIssueKeys = computed(() => pendingIssues.value.map(contextKey))
|
|
552
|
-
|
|
553
500
|
// Reset the form whenever the modal opens for a (new) container, and refresh the
|
|
554
501
|
// imported docs/issues so the quick-pick list is current.
|
|
555
502
|
watch(open, (isOpen) => {
|
|
@@ -591,8 +538,6 @@ watch(open, (isOpen) => {
|
|
|
591
538
|
pipelineId.value = DEFAULT_PIPELINE_FOR_TYPE[taskType.value] ?? ''
|
|
592
539
|
agentConfigValues.value = {}
|
|
593
540
|
pendingContext.value = []
|
|
594
|
-
showDocPicker.value = false
|
|
595
|
-
showIssuePicker.value = false
|
|
596
541
|
// Seed from a prefill when opened from another surface (e.g. "create task from
|
|
597
542
|
// issue" sets the title + stages the issue as linked context). Pipeline / preset
|
|
598
543
|
// are intentionally left at their defaults so the user confirms them here.
|
|
@@ -1211,202 +1156,15 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
|
|
|
1211
1156
|
/>
|
|
1212
1157
|
</div>
|
|
1213
1158
|
|
|
1214
|
-
<!-- Context documents
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
variant="soft"
|
|
1224
|
-
size="xs"
|
|
1225
|
-
:icon="showDocPicker ? 'i-lucide-x' : 'i-lucide-plus'"
|
|
1226
|
-
@click="
|
|
1227
|
-
() => {
|
|
1228
|
-
showDocPicker = !showDocPicker
|
|
1229
|
-
}
|
|
1230
|
-
"
|
|
1231
|
-
>
|
|
1232
|
-
{{ showDocPicker ? t('board.addTask.done') : t('board.addTask.attach') }}
|
|
1233
|
-
</UButton>
|
|
1234
|
-
<UDropdownMenu
|
|
1235
|
-
v-else-if="connectableDocSources.length > 1"
|
|
1236
|
-
:items="connectDocMenu"
|
|
1237
|
-
:content="{ side: 'bottom', align: 'end' }"
|
|
1238
|
-
>
|
|
1239
|
-
<UButton color="neutral" variant="soft" size="xs" icon="i-lucide-plug">
|
|
1240
|
-
{{ t('board.addTask.connectSource') }}
|
|
1241
|
-
</UButton>
|
|
1242
|
-
</UDropdownMenu>
|
|
1243
|
-
<UButton
|
|
1244
|
-
v-else-if="connectableDocSources.length === 1"
|
|
1245
|
-
color="neutral"
|
|
1246
|
-
variant="soft"
|
|
1247
|
-
size="xs"
|
|
1248
|
-
icon="i-lucide-plug"
|
|
1249
|
-
@click="ui.openDocumentConnect(connectableDocSources[0]!.source)"
|
|
1250
|
-
>
|
|
1251
|
-
{{
|
|
1252
|
-
t('board.addTask.connectSourceNamed', { source: connectableDocSources[0]!.label })
|
|
1253
|
-
}}
|
|
1254
|
-
</UButton>
|
|
1255
|
-
<UButton
|
|
1256
|
-
v-else
|
|
1257
|
-
color="neutral"
|
|
1258
|
-
variant="soft"
|
|
1259
|
-
size="xs"
|
|
1260
|
-
icon="i-lucide-plus"
|
|
1261
|
-
disabled
|
|
1262
|
-
:title="
|
|
1263
|
-
documents.available
|
|
1264
|
-
? t('board.addTask.attachDocDisabledConnect')
|
|
1265
|
-
: t('board.addTask.attachDocDisabledEnable')
|
|
1266
|
-
"
|
|
1267
|
-
>
|
|
1268
|
-
{{ t('board.addTask.attach') }}
|
|
1269
|
-
</UButton>
|
|
1270
|
-
</div>
|
|
1271
|
-
<ContextDocumentPicker
|
|
1272
|
-
v-if="showDocPicker && docsConnected"
|
|
1273
|
-
:chosen-keys="chosenDocKeys"
|
|
1274
|
-
@pick="addPending"
|
|
1275
|
-
/>
|
|
1276
|
-
<div v-if="pendingDocs.length" class="space-y-1">
|
|
1277
|
-
<div
|
|
1278
|
-
v-for="item in pendingDocs"
|
|
1279
|
-
:key="contextKey(item)"
|
|
1280
|
-
class="flex items-center gap-1.5 rounded-md border border-slate-800 bg-slate-900/60 px-2 py-1.5 text-xs text-slate-300"
|
|
1281
|
-
>
|
|
1282
|
-
<UIcon
|
|
1283
|
-
:name="item.icon ?? 'i-lucide-file-text'"
|
|
1284
|
-
class="h-3.5 w-3.5 shrink-0 text-indigo-400"
|
|
1285
|
-
/>
|
|
1286
|
-
<span class="truncate">{{ item.title }}</span>
|
|
1287
|
-
<UBadge
|
|
1288
|
-
v-if="item.needsImport"
|
|
1289
|
-
color="neutral"
|
|
1290
|
-
variant="soft"
|
|
1291
|
-
size="xs"
|
|
1292
|
-
class="ms-1 shrink-0"
|
|
1293
|
-
>
|
|
1294
|
-
{{ t('board.addTask.importsOnAdd') }}
|
|
1295
|
-
</UBadge>
|
|
1296
|
-
<button
|
|
1297
|
-
type="button"
|
|
1298
|
-
class="ms-auto shrink-0 text-slate-400 hover:text-slate-200"
|
|
1299
|
-
@click="removePending(item)"
|
|
1300
|
-
>
|
|
1301
|
-
<UIcon name="i-lucide-x" class="h-3.5 w-3.5" />
|
|
1302
|
-
</button>
|
|
1303
|
-
</div>
|
|
1304
|
-
</div>
|
|
1305
|
-
<p v-else class="text-[11px] text-slate-500">
|
|
1306
|
-
{{ t('board.addTask.noDocsHint') }}
|
|
1307
|
-
</p>
|
|
1308
|
-
</div>
|
|
1309
|
-
|
|
1310
|
-
<!-- Context issues (ungated; Attach disabled until a tracker is connected). -->
|
|
1311
|
-
<div class="space-y-2">
|
|
1312
|
-
<div class="flex items-center justify-between">
|
|
1313
|
-
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
1314
|
-
{{ t('board.addTask.contextIssues') }}
|
|
1315
|
-
</span>
|
|
1316
|
-
<UButton
|
|
1317
|
-
v-if="issuesConnected"
|
|
1318
|
-
color="neutral"
|
|
1319
|
-
variant="soft"
|
|
1320
|
-
size="xs"
|
|
1321
|
-
:icon="showIssuePicker ? 'i-lucide-x' : 'i-lucide-plus'"
|
|
1322
|
-
@click="
|
|
1323
|
-
() => {
|
|
1324
|
-
showIssuePicker = !showIssuePicker
|
|
1325
|
-
}
|
|
1326
|
-
"
|
|
1327
|
-
>
|
|
1328
|
-
{{ showIssuePicker ? t('board.addTask.done') : t('board.addTask.attach') }}
|
|
1329
|
-
</UButton>
|
|
1330
|
-
<UDropdownMenu
|
|
1331
|
-
v-else-if="connectableIssueSources.length > 1"
|
|
1332
|
-
:items="connectIssueMenu"
|
|
1333
|
-
:content="{ side: 'bottom', align: 'end' }"
|
|
1334
|
-
>
|
|
1335
|
-
<UButton color="neutral" variant="soft" size="xs" icon="i-lucide-plug">
|
|
1336
|
-
{{ t('board.addTask.connectSource') }}
|
|
1337
|
-
</UButton>
|
|
1338
|
-
</UDropdownMenu>
|
|
1339
|
-
<UButton
|
|
1340
|
-
v-else-if="connectableIssueSources.length === 1"
|
|
1341
|
-
color="neutral"
|
|
1342
|
-
variant="soft"
|
|
1343
|
-
size="xs"
|
|
1344
|
-
icon="i-lucide-plug"
|
|
1345
|
-
@click="ui.openTaskConnect(connectableIssueSources[0]!.source)"
|
|
1346
|
-
>
|
|
1347
|
-
{{
|
|
1348
|
-
t('board.addTask.connectSourceNamed', {
|
|
1349
|
-
source: connectableIssueSources[0]!.label,
|
|
1350
|
-
})
|
|
1351
|
-
}}
|
|
1352
|
-
</UButton>
|
|
1353
|
-
<UButton
|
|
1354
|
-
v-else
|
|
1355
|
-
color="neutral"
|
|
1356
|
-
variant="soft"
|
|
1357
|
-
size="xs"
|
|
1358
|
-
icon="i-lucide-plus"
|
|
1359
|
-
disabled
|
|
1360
|
-
:title="
|
|
1361
|
-
tasks.available
|
|
1362
|
-
? t('board.addTask.attachIssueDisabledConnect')
|
|
1363
|
-
: t('board.addTask.attachIssueDisabledEnable')
|
|
1364
|
-
"
|
|
1365
|
-
>
|
|
1366
|
-
{{ t('board.addTask.attach') }}
|
|
1367
|
-
</UButton>
|
|
1368
|
-
</div>
|
|
1369
|
-
<!-- The container is what scopes the issue search to a repo, so the picker is
|
|
1370
|
-
only rendered once we have one (the modal is open, so we always do). -->
|
|
1371
|
-
<ContextIssuePicker
|
|
1372
|
-
v-if="showIssuePicker && issuesConnected && ui.addTaskContainerId"
|
|
1373
|
-
:chosen-keys="chosenIssueKeys"
|
|
1374
|
-
:scope-block-id="ui.addTaskContainerId"
|
|
1375
|
-
@pick="addPending"
|
|
1376
|
-
/>
|
|
1377
|
-
<div v-if="pendingIssues.length" class="space-y-1">
|
|
1378
|
-
<div
|
|
1379
|
-
v-for="item in pendingIssues"
|
|
1380
|
-
:key="contextKey(item)"
|
|
1381
|
-
class="flex items-center gap-1.5 rounded-md border border-slate-800 bg-slate-900/60 px-2 py-1.5 text-xs text-slate-300"
|
|
1382
|
-
>
|
|
1383
|
-
<UIcon
|
|
1384
|
-
:name="item.icon ?? 'i-lucide-square-check'"
|
|
1385
|
-
class="h-3.5 w-3.5 shrink-0 text-indigo-400"
|
|
1386
|
-
/>
|
|
1387
|
-
<span class="truncate">{{ item.title }}</span>
|
|
1388
|
-
<UBadge
|
|
1389
|
-
v-if="item.needsImport"
|
|
1390
|
-
color="neutral"
|
|
1391
|
-
variant="soft"
|
|
1392
|
-
size="xs"
|
|
1393
|
-
class="ms-1 shrink-0"
|
|
1394
|
-
>
|
|
1395
|
-
{{ t('board.addTask.importsOnAdd') }}
|
|
1396
|
-
</UBadge>
|
|
1397
|
-
<button
|
|
1398
|
-
type="button"
|
|
1399
|
-
class="ms-auto shrink-0 text-slate-400 hover:text-slate-200"
|
|
1400
|
-
@click="removePending(item)"
|
|
1401
|
-
>
|
|
1402
|
-
<UIcon name="i-lucide-x" class="h-3.5 w-3.5" />
|
|
1403
|
-
</button>
|
|
1404
|
-
</div>
|
|
1405
|
-
</div>
|
|
1406
|
-
<p v-else class="text-[11px] text-slate-500">
|
|
1407
|
-
{{ t('board.addTask.noIssuesHint') }}
|
|
1408
|
-
</p>
|
|
1409
|
-
</div>
|
|
1159
|
+
<!-- Context documents + issues, staged here and linked once the task exists.
|
|
1160
|
+
Shared with the initiative create modal (ContextAttachmentFields). -->
|
|
1161
|
+
<ContextAttachmentFields
|
|
1162
|
+
v-if="ui.addTaskContainerId"
|
|
1163
|
+
v-model="pendingContext"
|
|
1164
|
+
:scope-block-id="ui.addTaskContainerId"
|
|
1165
|
+
:docs-hint="t('board.addTask.noDocsHint')"
|
|
1166
|
+
:issues-hint="t('board.addTask.noIssuesHint')"
|
|
1167
|
+
/>
|
|
1410
1168
|
|
|
1411
1169
|
<p class="text-[11px] text-slate-500">
|
|
1412
1170
|
{{ t('board.addTask.plannedHint') }}
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
// The preset form is rendered GENERICALLY from `descriptor.fields` (InitiativePresetFields) — zero
|
|
10
10
|
// per-preset frontend code. A preset with a repo-detection probe prefills its form from the frame's
|
|
11
11
|
// repo on selection (best-effort; failures fall back to descriptor defaults and never block create).
|
|
12
|
+
//
|
|
13
|
+
// The user can also attach CONTEXT — requirements, RFCs, PRDs, tracker issues — which the whole
|
|
14
|
+
// planning pipeline then reads: the interviewer stops asking what an attached document already
|
|
15
|
+
// answers, and the analyst and planner ground the plan in it. Linking needs a block id, so picks
|
|
16
|
+
// are staged and committed once the initiative exists (the add-task flow's shared orchestration).
|
|
12
17
|
import { computed, ref, watch } from 'vue'
|
|
13
18
|
import {
|
|
14
19
|
sanitizeInitiativePresetInputs,
|
|
@@ -18,12 +23,15 @@ import type { InitiativePresetInputs, InitiativePresetInputValue } from '~/types
|
|
|
18
23
|
import { defaultPresetInputs } from '~/utils/initiative'
|
|
19
24
|
import { GENERIC_PRESET_ID } from '~/stores/initiative'
|
|
20
25
|
import InitiativePresetFields from '~/components/board/InitiativePresetFields.vue'
|
|
26
|
+
import ContextAttachmentFields from '~/components/context/ContextAttachmentFields.vue'
|
|
27
|
+
import type { PendingContext } from '~/composables/useContextLinking'
|
|
21
28
|
|
|
22
29
|
const ui = useUiStore()
|
|
23
30
|
const board = useBoardStore()
|
|
24
31
|
const initiatives = useInitiativesStore()
|
|
25
32
|
const toast = useToast()
|
|
26
33
|
const { t } = useI18n()
|
|
34
|
+
const { linkPending, presentLinkFailures } = useContextLinking()
|
|
27
35
|
|
|
28
36
|
const open = computed({
|
|
29
37
|
get: () => ui.createInitiativeFrameId !== null,
|
|
@@ -45,6 +53,8 @@ const selectedPreset = computed(() => initiatives.presetById(selectedPresetId.va
|
|
|
45
53
|
const title = ref('')
|
|
46
54
|
const description = ref('')
|
|
47
55
|
const inputs = ref<InitiativePresetInputs>({})
|
|
56
|
+
// Context the user chose to attach, committed once the initiative block exists (see create()).
|
|
57
|
+
const pendingContext = ref<PendingContext[]>([])
|
|
48
58
|
|
|
49
59
|
// Monotonic token so a slow probe response from a since-changed preset/frame is discarded.
|
|
50
60
|
let probeSeq = 0
|
|
@@ -97,6 +107,7 @@ watch(open, (o) => {
|
|
|
97
107
|
if (!o) return
|
|
98
108
|
title.value = ''
|
|
99
109
|
description.value = ''
|
|
110
|
+
pendingContext.value = []
|
|
100
111
|
selectedPresetId.value = GENERIC_PRESET_ID
|
|
101
112
|
applyPreset()
|
|
102
113
|
})
|
|
@@ -123,6 +134,12 @@ async function create() {
|
|
|
123
134
|
? sanitizeInitiativePresetInputs(descriptor, inputs.value)
|
|
124
135
|
: undefined,
|
|
125
136
|
})
|
|
137
|
+
// Surface the SPECIFIC cause of any attachment that couldn't be linked (a GitHub
|
|
138
|
+
// permission/visibility error, a not-found doc, …) rather than a bare count. The initiative
|
|
139
|
+
// itself is already created, so a failed attachment never costs the user the form.
|
|
140
|
+
presentLinkFailures(await linkPending(block.id, pendingContext.value), block.id, {
|
|
141
|
+
title: (count) => t('initiative.create.linkFailed', { count }, count),
|
|
142
|
+
})
|
|
126
143
|
ui.closeCreateInitiative()
|
|
127
144
|
// Select the fresh block so the inspector offers "Run planning" right away.
|
|
128
145
|
ui.select(block.id)
|
|
@@ -216,6 +233,16 @@ async function create() {
|
|
|
216
233
|
:descriptor="selectedPreset"
|
|
217
234
|
/>
|
|
218
235
|
|
|
236
|
+
<!-- Attached requirements / issues, staged here and linked once the block exists. The
|
|
237
|
+
issue search is scoped to the service frame, which is what resolves its repo. -->
|
|
238
|
+
<ContextAttachmentFields
|
|
239
|
+
v-if="ui.createInitiativeFrameId"
|
|
240
|
+
v-model="pendingContext"
|
|
241
|
+
:scope-block-id="ui.createInitiativeFrameId"
|
|
242
|
+
:docs-hint="t('initiative.create.contextDocsHint')"
|
|
243
|
+
:issues-hint="t('initiative.create.contextIssuesHint')"
|
|
244
|
+
/>
|
|
245
|
+
|
|
219
246
|
<p class="text-[11px] text-slate-500">
|
|
220
247
|
{{ t('initiative.create.hint') }}
|
|
221
248
|
</p>
|
|
@@ -39,6 +39,7 @@ const {
|
|
|
39
39
|
planningPipeline,
|
|
40
40
|
running,
|
|
41
41
|
awaitingAnswers,
|
|
42
|
+
interviewing,
|
|
42
43
|
starting,
|
|
43
44
|
runPlanning,
|
|
44
45
|
openPlanning,
|
|
@@ -116,6 +117,19 @@ function onHandle(e: PointerEvent) {
|
|
|
116
117
|
>
|
|
117
118
|
{{ t('initiative.inspector.answerPlanning') }}
|
|
118
119
|
</UButton>
|
|
120
|
+
<!-- Mid-pass: nothing to answer, but keep the route into the window (it shows the wait). -->
|
|
121
|
+
<UButton
|
|
122
|
+
v-else-if="interviewing"
|
|
123
|
+
data-testid="initiative-card-planning-in-progress"
|
|
124
|
+
size="xs"
|
|
125
|
+
variant="soft"
|
|
126
|
+
color="neutral"
|
|
127
|
+
icon="i-lucide-loader-circle"
|
|
128
|
+
:ui="{ leadingIcon: 'animate-spin' }"
|
|
129
|
+
@click.stop="openPlanning"
|
|
130
|
+
>
|
|
131
|
+
{{ t('initiative.inspector.planningInProgress') }}
|
|
132
|
+
</UButton>
|
|
119
133
|
<UButton
|
|
120
134
|
v-else
|
|
121
135
|
data-testid="initiative-card-run-planning"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The shared "the interview is not waiting on you" panel, rendered by BOTH interview-gate windows
|
|
3
|
+
// (initiative planning, document interview) in place of the question list. It exists because those
|
|
4
|
+
// two states — a pass running, and a run that stopped before the interview settled — are the ones
|
|
5
|
+
// a window keyed on the interview entity alone cannot show at all, which is what made continue /
|
|
6
|
+
// proceed read as dead buttons. Copy is per-feature (passed in); the treatment is shared, so the
|
|
7
|
+
// two windows can't drift on how a wait or a failure looks. See
|
|
8
|
+
// `docs/initiatives/clarification-items.md`.
|
|
9
|
+
defineProps<{
|
|
10
|
+
/** `working` = a pass is in flight (spinner); `failed` = the run stopped (error treatment). */
|
|
11
|
+
variant: 'working' | 'failed'
|
|
12
|
+
/** The headline, already localized by the calling window. */
|
|
13
|
+
title: string
|
|
14
|
+
/** The explanatory line under it, already localized. */
|
|
15
|
+
hint: string
|
|
16
|
+
/** The window's own `data-testid`, so each keeps a stable selector. */
|
|
17
|
+
testid: string
|
|
18
|
+
}>()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div
|
|
23
|
+
v-if="variant === 'working'"
|
|
24
|
+
class="flex flex-col items-center gap-2 rounded-lg border border-slate-800 bg-slate-950/40 p-6 text-center"
|
|
25
|
+
:data-testid="testid"
|
|
26
|
+
>
|
|
27
|
+
<UIcon name="i-lucide-loader-circle" class="h-5 w-5 animate-spin text-indigo-300" />
|
|
28
|
+
<p class="text-[13px] text-slate-200">{{ title }}</p>
|
|
29
|
+
<p class="text-[12px] text-slate-400">{{ hint }}</p>
|
|
30
|
+
</div>
|
|
31
|
+
<div
|
|
32
|
+
v-else
|
|
33
|
+
class="rounded-lg border border-red-900/60 bg-red-950/20 p-4 text-center"
|
|
34
|
+
:data-testid="testid"
|
|
35
|
+
>
|
|
36
|
+
<p class="text-[13px] text-red-200">{{ title }}</p>
|
|
37
|
+
<p class="mt-1 text-[12px] text-slate-400">{{ hint }}</p>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|