refactor: 收口草稿生成作品架模型

This commit is contained in:
2026-06-03 20:11:25 +08:00
parent 69167da8d0
commit fe2f8a66e6
6 changed files with 1180 additions and 798 deletions

View File

@@ -0,0 +1,860 @@
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldWorkSummary';
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
import type { WoodenFishWorkSummaryResponse } from '../../../packages/shared/src/contracts/woodenFish';
import {
type CreationWorkShelfItem,
type CreationWorkShelfKind,
type CreationWorkShelfRuntimeState,
resolvePuzzleWorkCoverImageSrc,
} from '../custom-world-home/creationWorkShelf';
export type DraftGenerationNoticeStatus = 'generating' | 'ready' | 'failed';
export type DraftGenerationNotice = {
status: DraftGenerationNoticeStatus;
seen: boolean;
completedAtMs?: number;
message?: string;
};
export type DraftGenerationNoticeMap = Record<string, DraftGenerationNotice>;
export type PendingDraftShelfState = {
status: DraftGenerationNoticeStatus;
seen: boolean;
updatedAt: string;
title?: string;
summary?: string;
};
export type PendingDraftShelfKind = Exclude<CreationWorkShelfKind, 'rpg'>;
export type PendingDraftShelfMap = Partial<
Record<PendingDraftShelfKind, Record<string, PendingDraftShelfState>>
>;
export type PendingDraftShelfMetadata = {
title?: string | null;
summary?: string | null;
};
export type PlatformDraftGenerationVisibleShelfSources = {
rpgItems: readonly CustomWorldWorkSummary[];
bigFishItems: readonly BigFishWorkSummary[];
jumpHopItems: readonly JumpHopWorkSummaryResponse[];
woodenFishItems: readonly WoodenFishWorkSummaryResponse[];
match3dItems: readonly Match3DWorkSummary[];
squareHoleItems: readonly SquareHoleWorkSummary[];
puzzleItems: readonly PuzzleWorkSummary[];
visualNovelItems: readonly VisualNovelWorkSummary[];
barkBattleItems: readonly BarkBattleWorkSummary[];
babyObjectMatchItems: readonly BabyObjectMatchDraft[];
};
export function buildPuzzleResultProfileId(
sessionId: string | null | undefined,
) {
const stableSuffix = resolvePuzzleSessionStableSuffix(sessionId);
return stableSuffix ? `puzzle-profile-${stableSuffix}` : null;
}
export function buildPuzzleResultWorkId(
sessionId: string | null | undefined,
) {
const stableSuffix = resolvePuzzleSessionStableSuffix(sessionId);
return stableSuffix ? `puzzle-work-${stableSuffix}` : null;
}
export function buildDraftNoticeKey(
kind: CreationWorkShelfKind,
id: string,
) {
return `${kind}:${id}`;
}
export function collectDraftNoticeKeys(
kind: CreationWorkShelfKind,
ids: Array<string | null | undefined>,
) {
const keys = new Set<string>();
for (const id of ids) {
const normalizedId = id?.trim();
if (normalizedId) {
keys.add(buildDraftNoticeKey(kind, normalizedId));
}
}
return Array.from(keys);
}
export function normalizeDraftNoticeId(id: string | null | undefined) {
return id?.trim() || null;
}
export function normalizePendingDraftShelfLookupId(
kind: PendingDraftShelfKind,
id: string | null | undefined,
) {
const normalizedId = normalizeDraftNoticeId(id);
if (!normalizedId) {
return null;
}
const noticePrefix = `${kind}:`;
if (!normalizedId.startsWith(noticePrefix)) {
return normalizedId;
}
return normalizeDraftNoticeId(normalizedId.slice(noticePrefix.length));
}
export function createPendingDraftShelfState(
status: DraftGenerationNoticeStatus,
seen = false,
updatedAt = new Date().toISOString(),
metadata?: PendingDraftShelfMetadata,
): PendingDraftShelfState {
const title = metadata?.title?.trim();
const summary = metadata?.summary?.trim();
return {
status,
seen,
updatedAt,
...(title ? { title } : {}),
...(summary ? { summary } : {}),
};
}
export function buildDraftFailedShelfSummary(kind: CreationWorkShelfKind) {
switch (kind) {
case 'puzzle':
return '拼图草稿生成失败,可重新打开处理。';
case 'match3d':
return '玩法素材生成失败,可重新打开处理。';
case 'big-fish':
return '草稿生成失败,可重新打开处理。';
case 'square-hole':
return '挑战素材生成失败,可重新打开处理。';
case 'jump-hop':
return '跳一跳玩法草稿生成失败,可重新打开处理。';
case 'wooden-fish':
return '敲木鱼草稿生成失败,可重新打开处理。';
case 'visual-novel':
return '视觉小说草稿生成失败,可重新打开处理。';
case 'bark-battle':
return '声浪竞技素材生成失败,可重新打开处理。';
case 'baby-object-match':
return '宝贝识物草稿生成失败,可重新打开处理。';
default:
return '草稿生成失败,可重新打开处理。';
}
}
export function buildDraftCompletionDialogSource(
kind: CreationWorkShelfKind,
ids: Array<string | null | undefined>,
): string {
const sourceId = pickDraftCompletionDialogSourceId(ids);
switch (kind) {
case 'rpg':
return formatDraftTaskCompletionSource('RPG 草稿', sourceId);
case 'big-fish':
return formatDraftTaskCompletionSource('大鱼吃小鱼草稿', sourceId);
case 'match3d':
return formatDraftTaskCompletionSource('抓大鹅草稿', sourceId);
case 'square-hole':
return formatDraftTaskCompletionSource('方洞挑战草稿', sourceId);
case 'jump-hop':
return formatDraftTaskCompletionSource('跳一跳草稿', sourceId);
case 'wooden-fish':
return formatDraftTaskCompletionSource('敲木鱼草稿', sourceId);
case 'puzzle':
return formatDraftTaskCompletionSource('拼图草稿', sourceId);
case 'visual-novel':
return formatDraftTaskCompletionSource('视觉小说草稿', sourceId);
case 'bark-battle':
return formatDraftTaskCompletionSource('汪汪声浪草稿', sourceId);
case 'baby-object-match':
return formatDraftTaskCompletionSource('宝贝识物草稿', sourceId);
}
return formatDraftTaskCompletionSource('创作草稿', sourceId);
}
export function isDraftShelfSummaryPlaceholder(
value: string | null | undefined,
) {
const normalized = value?.trim();
if (!normalized) {
return true;
}
return /^(|.*$|$)/u.test(
normalized,
);
}
export function isPersistedDraftGenerating(value: string | null | undefined) {
return value?.trim() === 'generating';
}
export function isPersistedDraftFailed(value: string | null | undefined) {
const normalized = value?.trim();
return normalized === 'failed' || normalized === 'partial_failed';
}
export function getGenerationNoticeShelfKeys(
item: CreationWorkShelfItem,
): string[] {
switch (item.source.kind) {
case 'rpg':
return collectDraftNoticeKeys('rpg', [
item.id,
item.source.item.workId,
item.source.item.sessionId,
item.source.item.profileId,
]);
case 'big-fish':
return collectDraftNoticeKeys('big-fish', [
item.id,
item.source.item.workId,
item.source.item.sourceSessionId,
]);
case 'match3d':
return collectDraftNoticeKeys('match3d', [
item.id,
item.source.item.workId,
item.source.item.profileId,
item.source.item.sourceSessionId,
]);
case 'square-hole':
return collectDraftNoticeKeys('square-hole', [
item.id,
item.source.item.workId,
item.source.item.profileId,
item.source.item.sourceSessionId,
]);
case 'jump-hop':
return collectDraftNoticeKeys('jump-hop', [
item.id,
item.source.item.workId,
item.source.item.profileId,
item.source.item.sourceSessionId,
]);
case 'puzzle':
return collectDraftNoticeKeys('puzzle', [
item.id,
item.source.item.workId,
item.source.item.profileId,
item.source.item.sourceSessionId,
buildPuzzleResultWorkId(item.source.item.sourceSessionId),
buildPuzzleResultProfileId(item.source.item.sourceSessionId),
]);
case 'visual-novel':
return collectDraftNoticeKeys('visual-novel', [
item.id,
item.source.item.profileId,
]);
case 'baby-object-match':
return collectDraftNoticeKeys('baby-object-match', [
item.id,
item.source.item.profileId,
item.source.item.draftId,
]);
case 'bark-battle':
return collectDraftNoticeKeys('bark-battle', [
item.id,
item.source.item.workId,
item.source.item.draftId,
]);
case 'wooden-fish':
return collectDraftNoticeKeys('wooden-fish', [
item.id,
item.source.item.workId,
item.source.item.profileId,
item.source.item.sourceSessionId,
]);
default:
return [];
}
}
export function getDraftGenerationNotice(
notices: DraftGenerationNoticeMap,
keys: readonly string[],
) {
for (const key of keys) {
const notice = notices[key];
if (notice) {
return notice;
}
}
return null;
}
export function getPendingDraftShelfState(
pendingShelfItems: PendingDraftShelfMap,
kind: PendingDraftShelfKind,
keys: readonly string[],
) {
const entries = pendingShelfItems[kind];
if (!entries) {
return null;
}
for (const key of keys) {
const normalizedKey = normalizePendingDraftShelfLookupId(kind, key);
const pending = normalizedKey ? entries[normalizedKey] : null;
if (pending) {
return pending;
}
}
return null;
}
export function hasDraftGenerationNoticeStatus(
notices: DraftGenerationNoticeMap,
kind: CreationWorkShelfKind,
ids: Array<string | null | undefined>,
status: DraftGenerationNoticeStatus,
) {
return collectDraftNoticeKeys(kind, ids).some(
(key) => notices[key]?.status === status,
);
}
export function hasUnreadReadyDraftGenerationNotice(
notices: DraftGenerationNoticeMap,
kind: CreationWorkShelfKind,
ids: Array<string | null | undefined>,
) {
return collectDraftNoticeKeys(kind, ids).some((key) => {
const notice = notices[key];
return notice?.status === 'ready' && !notice.seen;
});
}
export function buildCreationWorkShelfRuntimeState(params: {
item: CreationWorkShelfItem;
notices: DraftGenerationNoticeMap;
pendingShelfItems: PendingDraftShelfMap;
}): CreationWorkShelfRuntimeState {
const { item, notices, pendingShelfItems } = params;
const noticeKeys = getGenerationNoticeShelfKeys(item);
const notice = getDraftGenerationNotice(notices, noticeKeys);
if (notice?.status === 'failed') {
const failedSummary = buildDraftFailedShelfSummary(item.source.kind);
const pending =
item.source.kind === 'rpg'
? null
: getPendingDraftShelfState(
pendingShelfItems,
item.source.kind,
noticeKeys,
);
const pendingSummary = pending?.summary?.trim();
return {
isGenerating: false,
hasGenerationFailure: true,
generationFailureSummary: failedSummary,
hasUnreadUpdate: false,
suppressPersistedGenerating: true,
titleOverride:
item.source.kind === 'puzzle' &&
item.status === 'draft' &&
!item.source.item.workTitle?.trim()
? '拼图草稿'
: undefined,
summaryOverride: isDraftShelfSummaryPlaceholder(item.summary)
? (pendingSummary ?? failedSummary)
: undefined,
};
}
if (
item.source.kind === 'puzzle' &&
isPersistedDraftFailed(item.source.item.generationStatus)
) {
const failedSummary = buildDraftFailedShelfSummary('puzzle');
return {
isGenerating: false,
hasGenerationFailure: true,
generationFailureSummary: failedSummary,
hasUnreadUpdate: false,
suppressPersistedGenerating: true,
titleOverride:
item.status === 'draft' && !item.source.item.workTitle?.trim()
? '拼图草稿'
: undefined,
summaryOverride: isDraftShelfSummaryPlaceholder(item.summary)
? failedSummary
: undefined,
};
}
const isNoticeGenerating =
notice?.status === 'generating' &&
(item.source.kind !== 'puzzle' ||
!resolvePuzzleWorkCoverImageSrc(item.source.item));
return {
isGenerating: isNoticeGenerating || item.isGenerating === true,
hasUnreadUpdate: notice?.status === 'ready' && !notice.seen,
};
}
export function collectVisibleDraftNoticeKeys(
sources: PlatformDraftGenerationVisibleShelfSources,
) {
return [
...sources.rpgItems.flatMap((item) =>
collectDraftNoticeKeys('rpg', [
item.workId,
item.sessionId,
item.profileId,
]),
),
...sources.bigFishItems.flatMap((item) =>
collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]),
),
...sources.jumpHopItems.flatMap((item) =>
collectDraftNoticeKeys('jump-hop', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
),
...sources.woodenFishItems.flatMap((item) =>
collectDraftNoticeKeys('wooden-fish', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
),
...sources.match3dItems.flatMap((item) =>
collectDraftNoticeKeys('match3d', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
),
...sources.squareHoleItems.flatMap((item) =>
collectDraftNoticeKeys('square-hole', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
),
...sources.puzzleItems.flatMap((item) =>
collectDraftNoticeKeys('puzzle', [
item.workId,
item.profileId,
item.sourceSessionId,
buildPuzzleResultWorkId(item.sourceSessionId),
buildPuzzleResultProfileId(item.sourceSessionId),
]),
),
...sources.visualNovelItems.flatMap((item) =>
collectDraftNoticeKeys('visual-novel', [item.profileId]),
),
...sources.barkBattleItems.flatMap((item) =>
collectDraftNoticeKeys('bark-battle', [item.workId, item.draftId]),
),
...sources.babyObjectMatchItems.flatMap((item) =>
collectDraftNoticeKeys('baby-object-match', [
item.profileId,
item.draftId,
]),
),
];
}
export function hasUnreadDraftGenerationUpdates(
notices: DraftGenerationNoticeMap,
visibleKeys: readonly string[],
) {
return visibleKeys.some((key) => {
const notice = notices[key];
return notice?.status === 'ready' && !notice.seen;
});
}
export function buildPendingBigFishWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly BigFishWorkSummary[],
): BigFishWorkSummary[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => {
const isFailed = state.status === 'failed';
return {
workId: `big-fish-work-${sessionId}`,
sourceSessionId: sessionId,
ownerUserId: '',
authorDisplayName: '',
title: '大鱼吃小鱼草稿',
subtitle: isFailed ? '生成失败待重试' : '草稿生成中',
summary: isFailed
? '草稿生成失败,可重新打开处理。'
: '正在生成玩法草稿。',
coverImageSrc: null,
status: 'draft',
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
levelCount: 0,
levelMainImageReadyCount: 0,
levelMotionReadyCount: 0,
backgroundReady: false,
playCount: 0,
remixCount: 0,
likeCount: 0,
};
});
}
export function buildPendingJumpHopWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly JumpHopWorkSummaryResponse[],
): JumpHopWorkSummaryResponse[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => {
const generationStatus =
state.status === 'failed'
? 'failed'
: state.status === 'generating'
? 'generating'
: 'ready';
return {
runtimeKind: 'jump-hop',
workId: `jump-hop-work-${sessionId}`,
profileId: `jump-hop-profile-${sessionId}`,
ownerUserId: '',
sourceSessionId: sessionId,
workTitle: '跳一跳草稿',
workDescription:
state.status === 'failed'
? '跳一跳玩法草稿生成失败,可重新打开处理。'
: '正在生成跳一跳玩法草稿。',
themeTags: [],
difficulty: 'standard',
stylePreset: 'minimal-blocks',
coverImageSrc: null,
publicationStatus: 'draft',
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
generationStatus,
};
});
}
export function buildPendingWoodenFishWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly WoodenFishWorkSummaryResponse[],
): WoodenFishWorkSummaryResponse[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => {
const generationStatus =
state.status === 'failed'
? 'failed'
: state.status === 'generating'
? 'generating'
: 'ready';
return {
runtimeKind: 'wooden-fish',
workId: `wooden-fish-work-${sessionId}`,
profileId: sessionId,
ownerUserId: '',
sourceSessionId: sessionId,
workTitle: '敲木鱼草稿',
workDescription:
state.status === 'failed'
? '敲木鱼草稿生成失败,可重新打开处理。'
: '正在生成敲木鱼草稿。',
themeTags: ['敲木鱼'],
coverImageSrc: null,
publicationStatus: 'draft',
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
generationStatus,
};
});
}
export function buildPendingMatch3DWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly Match3DWorkSummary[],
): Match3DWorkSummary[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => {
const themeText = state.summary?.trim() || state.title?.trim() || '';
const fallbackSummary =
state.status === 'failed'
? '玩法素材生成失败,可重新打开处理。'
: '正在生成玩法素材。';
return {
workId: `match3d-work-${sessionId}`,
profileId: sessionId,
ownerUserId: '',
sourceSessionId: sessionId,
gameName: '抓大鹅草稿',
themeText,
summary: themeText || fallbackSummary,
tags: [],
coverImageSrc: null,
referenceImageSrc: null,
clearCount: 0,
difficulty: 0,
publicationStatus: 'draft',
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
generationStatus:
state.status === 'failed'
? 'failed'
: state.status === 'generating'
? 'generating'
: 'ready',
generatedItemAssets: [],
};
});
}
export function buildPendingSquareHoleWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly SquareHoleWorkSummary[],
): SquareHoleWorkSummary[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => ({
workId: `square-hole-work-${sessionId}`,
profileId: sessionId,
ownerUserId: '',
sourceSessionId: sessionId,
gameName: '方洞挑战草稿',
themeText: '',
twistRule: '',
summary:
state.status === 'failed'
? '挑战素材生成失败,可重新打开处理。'
: '正在生成挑战素材。',
tags: [],
coverImageSrc: null,
backgroundPrompt: '',
backgroundImageSrc: null,
shapeOptions: [],
holeOptions: [],
shapeCount: 0,
difficulty: 0,
publicationStatus: 'draft',
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
}));
}
export function buildPendingPuzzleWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly PuzzleWorkSummary[],
): PuzzleWorkSummary[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => {
const profileId =
buildPuzzleResultProfileId(sessionId) ?? `puzzle-profile-${sessionId}`;
const title = state.title?.trim() || '拼图草稿';
const summary =
state.summary?.trim() ||
(state.status === 'failed'
? '拼图草稿生成失败,可重新打开处理。'
: '正在生成拼图草稿。');
return {
workId:
buildPuzzleResultWorkId(sessionId) ?? `puzzle-work-${sessionId}`,
profileId,
ownerUserId: '',
sourceSessionId: sessionId,
authorDisplayName: '',
workTitle: title,
workDescription: summary,
levelName: title,
summary,
themeTags: [],
coverImageSrc: null,
coverAssetId: null,
publicationStatus: 'draft',
updatedAt: state.updatedAt,
publishedAt: null,
playCount: 0,
remixCount: 0,
likeCount: 0,
publishReady: false,
generationStatus:
state.status === 'generating'
? 'generating'
: state.status === 'failed'
? 'failed'
: 'ready',
levels: [],
};
});
}
export function buildPendingVisualNovelWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly VisualNovelWorkSummary[],
): VisualNovelWorkSummary[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([profileId]) =>
existingItems.every((item) => item.profileId !== profileId),
)
.map(([profileId, state]) => ({
runtimeKind: 'visual-novel',
profileId,
ownerUserId: '',
title: '视觉小说草稿',
description:
state.status === 'failed'
? '视觉小说草稿生成失败,可重新打开处理。'
: '正在生成视觉小说草稿。',
coverImageSrc: null,
tags: [],
publishStatus: 'draft',
publishReady: false,
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
}));
}
export function buildPendingBarkBattleWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly BarkBattleWorkSummary[],
): BarkBattleWorkSummary[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([id]) =>
existingItems.every((item) => item.workId !== id && item.draftId !== id),
)
.map(([id, state]) => ({
workId: id,
draftId: id,
ownerUserId: '',
authorDisplayName: '',
title: '汪汪声浪草稿',
summary:
state.status === 'failed'
? '声浪竞技素材生成失败,可重新打开处理。'
: '正在生成声浪竞技素材。',
themeDescription: '',
playerImageDescription: '',
opponentImageDescription: '',
onomatopoeia: [],
playerCharacterImageSrc: null,
opponentCharacterImageSrc: null,
uiBackgroundImageSrc: null,
difficultyPreset: 'normal',
status: 'draft',
generationStatus:
state.status === 'generating'
? 'pending_assets'
: state.status === 'failed'
? 'partial_failed'
: 'ready',
publishReady: false,
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
}));
}
function resolvePuzzleSessionStableSuffix(
sessionId: string | null | undefined,
) {
const normalizedSessionId = sessionId?.trim();
if (!normalizedSessionId) {
return null;
}
return normalizedSessionId.startsWith('puzzle-session-')
? normalizedSessionId.slice('puzzle-session-'.length)
: normalizedSessionId;
}
function pickDraftCompletionDialogSourceId(
ids: Array<string | null | undefined>,
) {
const normalizedIds = ids
.map((id) => id?.trim() ?? '')
.filter((id) => Boolean(id));
return (
normalizedIds.find((id) => /session/i.test(id)) ??
normalizedIds.find((id) => /work/i.test(id)) ??
normalizedIds.find((id) => /draft/i.test(id)) ??
normalizedIds.find((id) => /run/i.test(id)) ??
normalizedIds.find((id) => /profile/i.test(id)) ??
normalizedIds[0] ??
null
);
}
function formatDraftTaskCompletionSource(label: string, id?: string | null) {
const normalizedId = id?.trim();
return normalizedId ? `${label} ${normalizedId}` : label;
}