1const NEW_TASK_DRAFT_PREFIX = "new-task:";
2
3/** Every new-task draft key: `new-task:<draftId>`. */
4export function newTaskDraftKey(draftId: string): string {
5 return `${NEW_TASK_DRAFT_PREFIX}${draftId}`;
6}
7
8export function isNewTaskDraftKey(draftKey: string): boolean {
9 return draftKey.startsWith(NEW_TASK_DRAFT_PREFIX);
10}
11
12/**
13 * The draft a rejected queued task's content is restored into. The outbox
14 * drain writes it and the thread screen's "Edit task" action opens it, so both
15 * sides derive the key here rather than rebuilding the string.
16 */
17export function restoredNewTaskDraftKey(messageId: string): string {
18 return newTaskDraftKey(`restored-${messageId}`);
19}
20
21/**
22 * Builds before drafts were id-keyed used `new-task:<environmentId>:<projectId>`,
23 * one slot per project. Ids are UUIDs and never contain a colon, so a colon
24 * after the prefix marks the legacy shape. Returns the split scope, or null
25 * when the key is not legacy.
26 */
27export function parseLegacyNewTaskDraftKey(
28 draftKey: string,
29): { readonly environmentId: string; readonly projectId: string } | null {
30 if (!isNewTaskDraftKey(draftKey)) {
31 return null;
32 }
33 const scope = draftKey.slice(NEW_TASK_DRAFT_PREFIX.length);
34 const separator = scope.lastIndexOf(":");
35 if (separator <= 0 || separator === scope.length - 1) {
36 return null;
37 }
38 return { environmentId: scope.slice(0, separator), projectId: scope.slice(separator + 1) };
39}