@taskforcehq/taskforce 0.3.301 → 0.3.302
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/dist/Taskforce.module.css +26 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +13 -24
- package/dist/TaskforceCore.test.js +167 -152
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.js +9 -8
- package/dist/components/views/StandaloneLayout.js +466 -204
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +49 -0
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +241 -78
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +5 -0
- package/dist/hooks/useTaskforce.js +63 -15
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +41 -19
- package/dist/mcp/runtime.test.js +49 -2
- package/dist/migrations/taskSchemaMigrations.js +53 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +15 -1
- package/dist/server/index.test.js +14 -1
- package/dist/server/routes/admin.js +25 -22
- package/dist/server/routes/auth.d.ts +5 -0
- package/dist/server/routes/auth.js +350 -16
- package/dist/server/routes/billing.js +11 -1
- package/dist/server/routes/billing.test.js +32 -2
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +11 -3
- package/dist/server/routes/sync.integration.test.js +130 -0
- package/dist/server/routes/sync.js +226 -1924
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +26 -19
- package/dist/server/routes.test.js +352 -6
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +88 -0
- package/dist/sync/contentSyncState.js +743 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +28 -0
- package/dist/sync/workspacePullFeed.js +301 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +166 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-JG5jc3he.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-iys3zfca.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-DJvLJI3A.js} +1 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-BguHeQaV.js} +1 -1
- package/dist/ui/assets/index-MXeWoebC.css +1 -0
- package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/billingStatusCache.d.ts +21 -0
- package/dist/utils/billingStatusCache.js +70 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +2 -1
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
|
@@ -21,6 +21,7 @@ export interface SyncReconciliationServiceDeps {
|
|
|
21
21
|
persistBackup?: boolean;
|
|
22
22
|
workspaceId?: string;
|
|
23
23
|
allowClientProvidedId?: boolean;
|
|
24
|
+
preferIncomingReferenceNumberOnConflict?: boolean;
|
|
24
25
|
skipRecompute?: boolean;
|
|
25
26
|
syncUpdatedAt?: string;
|
|
26
27
|
actorRef?: string;
|
|
@@ -28,6 +29,7 @@ export interface SyncReconciliationServiceDeps {
|
|
|
28
29
|
}) => TaskItem;
|
|
29
30
|
updateTask: (id: string, updates: Partial<TaskItem>, workspaceId?: string, options?: {
|
|
30
31
|
persistBackup?: boolean;
|
|
32
|
+
preferIncomingReferenceNumberOnConflict?: boolean;
|
|
31
33
|
skipRecompute?: boolean;
|
|
32
34
|
syncUpdatedAt?: string;
|
|
33
35
|
actorRef?: string;
|
|
@@ -198,6 +198,7 @@ export function applyWorkspaceSyncSnapshotService(deps, input, workspaceId) {
|
|
|
198
198
|
createdBy: sourceTask.createdBy === undefined ? existing?.createdBy : sourceTask.createdBy,
|
|
199
199
|
assignee: sourceTask.assignee === undefined ? existing?.assignee : sourceTask.assignee,
|
|
200
200
|
referenceNumber: sourceTask.referenceNumber === undefined ? existing?.referenceNumber : sourceTask.referenceNumber,
|
|
201
|
+
localReferenceNumber: sourceTask.referenceNumber === undefined ? existing?.localReferenceNumber : null,
|
|
201
202
|
scheduledDate: sourceTask.scheduledDate === undefined ? existing?.scheduledDate : sourceTask.scheduledDate,
|
|
202
203
|
dueDate: sourceTask.dueDate === undefined ? existing?.dueDate : sourceTask.dueDate,
|
|
203
204
|
scheduledWeekKey: sourceTask.scheduledWeekKey === undefined ? existing?.scheduledWeekKey : sourceTask.scheduledWeekKey,
|
|
@@ -228,6 +229,7 @@ export function applyWorkspaceSyncSnapshotService(deps, input, workspaceId) {
|
|
|
228
229
|
persistBackup: false,
|
|
229
230
|
workspaceId: normalizedWorkspaceId,
|
|
230
231
|
allowClientProvidedId: true,
|
|
232
|
+
preferIncomingReferenceNumberOnConflict: true,
|
|
231
233
|
skipRecompute: true,
|
|
232
234
|
syncUpdatedAt: incomingUpdatedAt,
|
|
233
235
|
skipTaskEvents: true
|
|
@@ -244,6 +246,7 @@ export function applyWorkspaceSyncSnapshotService(deps, input, workspaceId) {
|
|
|
244
246
|
try {
|
|
245
247
|
updatedTask = deps.updateTask(taskId, taskPatch, normalizedWorkspaceId, {
|
|
246
248
|
persistBackup: false,
|
|
249
|
+
preferIncomingReferenceNumberOnConflict: true,
|
|
247
250
|
skipRecompute: true,
|
|
248
251
|
syncUpdatedAt: incomingUpdatedAt,
|
|
249
252
|
skipTaskEvents: true
|
|
@@ -92,6 +92,46 @@ describe('TaskforceCore task audit timeline', () => {
|
|
|
92
92
|
cleanup();
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
|
+
it('suppresses task events for autosave-style updates when skipTaskEvents is true', () => {
|
|
96
|
+
const { core, cleanup } = makeCore();
|
|
97
|
+
try {
|
|
98
|
+
const task = core.createTask({ title: 'Autosave task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
99
|
+
core.updateTask(task.id, { description: 'Draft body' }, 'workspace-a', {
|
|
100
|
+
actorRef: 'user',
|
|
101
|
+
skipTaskEvents: true
|
|
102
|
+
});
|
|
103
|
+
const events = core.listTaskEvents(task.id, 'workspace-a');
|
|
104
|
+
const hydrated = core.getTask(task.id, 'workspace-a');
|
|
105
|
+
expect(events.map((event) => event.action)).toEqual(['task-created']);
|
|
106
|
+
expect(hydrated?.description).toBe('Draft body');
|
|
107
|
+
expect(hydrated?.activity?.some((item) => item.type === 'event' && item.event.action === 'task-updated')).toBe(false);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
cleanup();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
it('captures assignment changes in the core task update event details', () => {
|
|
114
|
+
const { core, cleanup } = makeCore();
|
|
115
|
+
try {
|
|
116
|
+
const task = core.createTask({ title: 'Assignment audit task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
117
|
+
core.updateTask(task.id, { assignee: 'member-1' }, 'workspace-a', { actorRef: 'user' });
|
|
118
|
+
const assignmentEvent = core.listTaskEvents(task.id, 'workspace-a').find((event) => event.action === 'task-updated' && event.details?.changes?.assignee);
|
|
119
|
+
expect(assignmentEvent).toEqual(expect.objectContaining({
|
|
120
|
+
action: 'task-updated',
|
|
121
|
+
details: {
|
|
122
|
+
changes: {
|
|
123
|
+
assignee: {
|
|
124
|
+
from: 'unassigned',
|
|
125
|
+
to: 'member-1',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
cleanup();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
95
135
|
it('repairs legacy comments tables so raw submitted authors can be stored', () => {
|
|
96
136
|
const { core, cleanup } = makeCore();
|
|
97
137
|
try {
|
package/dist/core/Taskforce.d.ts
CHANGED
|
@@ -86,6 +86,7 @@ export interface TaskActor {
|
|
|
86
86
|
export interface TaskItem {
|
|
87
87
|
id: string;
|
|
88
88
|
referenceNumber?: number | null;
|
|
89
|
+
localReferenceNumber?: number | null;
|
|
89
90
|
referenceLabel?: string;
|
|
90
91
|
title: string;
|
|
91
92
|
description: string | null;
|
|
@@ -367,9 +368,24 @@ interface UserIdentityRecord {
|
|
|
367
368
|
userId: string;
|
|
368
369
|
email: string;
|
|
369
370
|
displayName?: string | null;
|
|
371
|
+
avatarUrl?: string | null;
|
|
370
372
|
systemRole?: SystemRole;
|
|
371
373
|
betaAccess?: boolean;
|
|
372
374
|
}
|
|
375
|
+
interface UserAvatarUploadDraftRecord {
|
|
376
|
+
draftId: string;
|
|
377
|
+
userId: string;
|
|
378
|
+
storageKey: string;
|
|
379
|
+
mimeType: string;
|
|
380
|
+
originalFilename: string;
|
|
381
|
+
sizeBytes: number;
|
|
382
|
+
storageProvider: 'local' | 'r2' | 'external';
|
|
383
|
+
finalizedAt?: string | null;
|
|
384
|
+
consumedAt?: string | null;
|
|
385
|
+
discardedAt?: string | null;
|
|
386
|
+
createdAt: string;
|
|
387
|
+
updatedAt: string;
|
|
388
|
+
}
|
|
373
389
|
interface WorkspaceUserRecord {
|
|
374
390
|
userId: string;
|
|
375
391
|
email: string;
|
|
@@ -476,6 +492,8 @@ interface CreateTaskOptions {
|
|
|
476
492
|
persistBackup?: boolean;
|
|
477
493
|
workspaceId?: string;
|
|
478
494
|
allowClientProvidedId?: boolean;
|
|
495
|
+
provisionalLocalReference?: boolean;
|
|
496
|
+
preferIncomingReferenceNumberOnConflict?: boolean;
|
|
479
497
|
skipRecompute?: boolean;
|
|
480
498
|
syncUpdatedAt?: string;
|
|
481
499
|
actorRef?: string;
|
|
@@ -971,7 +989,9 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
971
989
|
private rowToWorkstream;
|
|
972
990
|
private rowToChecklistItem;
|
|
973
991
|
private sanitizeTaskReferenceNumber;
|
|
992
|
+
private getTaskReferencePolicyDeps;
|
|
974
993
|
private getTaskByReferenceNumber;
|
|
994
|
+
private getTaskByLocalReferenceNumber;
|
|
975
995
|
resolveTaskIdentifier(value: string, workspaceId?: string): TaskItem | null;
|
|
976
996
|
getInitiative(id: string, workspaceId?: string): InitiativeItem | null;
|
|
977
997
|
listInitiatives(workspaceId?: string): InitiativeItem[];
|
|
@@ -989,6 +1009,15 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
989
1009
|
upsertWorkstreamForSync(workstream: WorkstreamItem, workspaceId?: string | null): WorkstreamItem;
|
|
990
1010
|
listChecklistItems(taskId: string, workspaceId?: string): ChecklistItem[];
|
|
991
1011
|
replaceChecklistItems(taskId: string, items: Array<Partial<ChecklistItem>>, workspaceId?: string): ChecklistItem[];
|
|
1012
|
+
private getTaskReferenceSequenceValue;
|
|
1013
|
+
private getLocalTaskReferenceSequenceValue;
|
|
1014
|
+
private getMaxTaskReferenceNumber;
|
|
1015
|
+
private getMaxLocalTaskReferenceNumber;
|
|
1016
|
+
private persistTaskReferenceSequence;
|
|
1017
|
+
private persistLocalTaskReferenceSequence;
|
|
1018
|
+
private allocateNextTaskReferenceNumber;
|
|
1019
|
+
private allocateNextLocalTaskReferenceNumber;
|
|
1020
|
+
private relocateTaskReferenceConflict;
|
|
992
1021
|
private reserveTaskReferenceNumber;
|
|
993
1022
|
private reserveGenericReferenceNumber;
|
|
994
1023
|
private reserveInitiativeReferenceNumber;
|
|
@@ -1081,7 +1110,25 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1081
1110
|
}): UserAccessRecord;
|
|
1082
1111
|
resolveUserAccess(userId: string, workspaceId: string, fallbackRole?: AccessRole): UserAccessRecord | null;
|
|
1083
1112
|
resolveUserIdentity(userId: string): UserIdentityRecord | null;
|
|
1113
|
+
updateUserProfile(userId: string, input: {
|
|
1114
|
+
displayName: string;
|
|
1115
|
+
avatarUrl?: string | null;
|
|
1116
|
+
}): UserIdentityRecord | null;
|
|
1084
1117
|
updateUserDisplayName(userId: string, displayName: string): UserIdentityRecord | null;
|
|
1118
|
+
createUserAvatarUploadDraft(input: {
|
|
1119
|
+
userId: string;
|
|
1120
|
+
draftId: string;
|
|
1121
|
+
storageKey: string;
|
|
1122
|
+
mimeType: string;
|
|
1123
|
+
originalFilename: string;
|
|
1124
|
+
sizeBytes: number;
|
|
1125
|
+
storageProvider?: 'local' | 'r2' | 'external';
|
|
1126
|
+
}): UserAvatarUploadDraftRecord;
|
|
1127
|
+
getUserAvatarUploadDraft(userId: string, draftId: string): UserAvatarUploadDraftRecord | null;
|
|
1128
|
+
markUserAvatarUploadDraftFinalized(userId: string, draftId: string): UserAvatarUploadDraftRecord | null;
|
|
1129
|
+
markUserAvatarUploadDraftConsumed(userId: string, draftId: string): UserAvatarUploadDraftRecord | null;
|
|
1130
|
+
discardUserAvatarUploadDraft(userId: string, draftId: string): UserAvatarUploadDraftRecord | null;
|
|
1131
|
+
cleanupStaleUserAvatarUploadDrafts(userId: string, olderThanIso: string): UserAvatarUploadDraftRecord[];
|
|
1085
1132
|
getSystemRole(userId: string): SystemRole;
|
|
1086
1133
|
getUserBetaAccess(userId: string): boolean;
|
|
1087
1134
|
countSystemAdmins(): number;
|
|
@@ -1602,9 +1649,11 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1602
1649
|
validateOnly?: boolean;
|
|
1603
1650
|
workspaceId?: string;
|
|
1604
1651
|
allowClientProvidedId?: boolean;
|
|
1652
|
+
provisionalLocalReference?: boolean;
|
|
1605
1653
|
}): BulkCreateTaskResult;
|
|
1606
1654
|
updateTask(id: string, updates: Partial<TaskItem>, workspaceId?: string, options?: {
|
|
1607
1655
|
persistBackup?: boolean;
|
|
1656
|
+
preferIncomingReferenceNumberOnConflict?: boolean;
|
|
1608
1657
|
skipRecompute?: boolean;
|
|
1609
1658
|
syncUpdatedAt?: string;
|
|
1610
1659
|
actorRef?: string;
|
|
@@ -80,6 +80,139 @@ describe('TaskforceCore id policy', () => {
|
|
|
80
80
|
cleanup();
|
|
81
81
|
}
|
|
82
82
|
});
|
|
83
|
+
it('assigns provisional local task references without consuming canonical task references', () => {
|
|
84
|
+
const { core, cleanup } = makeCore();
|
|
85
|
+
try {
|
|
86
|
+
const provisionalFirst = core.createTask({ title: 'Offline first' }, {
|
|
87
|
+
workspaceId: 'workspace-a',
|
|
88
|
+
provisionalLocalReference: true
|
|
89
|
+
});
|
|
90
|
+
const provisionalSecond = core.createTask({ title: 'Offline second' }, {
|
|
91
|
+
workspaceId: 'workspace-a',
|
|
92
|
+
provisionalLocalReference: true
|
|
93
|
+
});
|
|
94
|
+
const canonical = core.createTask({ title: 'Cloud-backed task' }, { workspaceId: 'workspace-a' });
|
|
95
|
+
expect(provisionalFirst.referenceNumber).toBeNull();
|
|
96
|
+
expect(provisionalFirst.localReferenceNumber).toBe(1);
|
|
97
|
+
expect(provisionalFirst.referenceLabel).toBe('LT-1');
|
|
98
|
+
expect(provisionalSecond.referenceNumber).toBeNull();
|
|
99
|
+
expect(provisionalSecond.localReferenceNumber).toBe(2);
|
|
100
|
+
expect(provisionalSecond.referenceLabel).toBe('LT-2');
|
|
101
|
+
expect(canonical.referenceNumber).toBe(1);
|
|
102
|
+
expect(canonical.localReferenceNumber).toBeNull();
|
|
103
|
+
expect(canonical.referenceLabel).toBe('T-1');
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
cleanup();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
it('replaces a provisional local reference with a canonical cloud reference during sync apply', () => {
|
|
110
|
+
const { core, cleanup } = makeCore();
|
|
111
|
+
try {
|
|
112
|
+
const provisional = core.createTask({ title: 'Offline draft' }, {
|
|
113
|
+
workspaceId: 'workspace-a',
|
|
114
|
+
provisionalLocalReference: true
|
|
115
|
+
});
|
|
116
|
+
expect(provisional.referenceLabel).toBe('LT-1');
|
|
117
|
+
const syncResult = core.applyWorkspaceSyncSnapshot({
|
|
118
|
+
tasks: [
|
|
119
|
+
{
|
|
120
|
+
id: provisional.id,
|
|
121
|
+
title: 'Offline draft',
|
|
122
|
+
status: 'task',
|
|
123
|
+
referenceNumber: 7,
|
|
124
|
+
updatedAt: '2099-04-10T17:00:00.000Z'
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}, 'workspace-a');
|
|
128
|
+
expect(syncResult.updated).toBe(1);
|
|
129
|
+
expect(core.getTask(provisional.id, 'workspace-a')).toEqual(expect.objectContaining({
|
|
130
|
+
referenceNumber: 7,
|
|
131
|
+
localReferenceNumber: null,
|
|
132
|
+
referenceLabel: 'T-7'
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
cleanup();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
it('preserves provisional local references across core reloads', () => {
|
|
140
|
+
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-id-reload-test-'));
|
|
141
|
+
const storagePath = path.join(projectRoot, '.taskforce');
|
|
142
|
+
const firstCore = new TaskforceCore({ projectRoot, storagePath });
|
|
143
|
+
try {
|
|
144
|
+
const provisional = firstCore.createTask({ title: 'Offline draft' }, {
|
|
145
|
+
workspaceId: 'workspace-a',
|
|
146
|
+
provisionalLocalReference: true
|
|
147
|
+
});
|
|
148
|
+
expect(provisional.referenceNumber).toBeNull();
|
|
149
|
+
expect(provisional.localReferenceNumber).toBe(1);
|
|
150
|
+
firstCore.close();
|
|
151
|
+
const reloadedCore = new TaskforceCore({ projectRoot, storagePath });
|
|
152
|
+
try {
|
|
153
|
+
const reloadedTask = reloadedCore.getTask(provisional.id, 'workspace-a');
|
|
154
|
+
expect(reloadedTask).toEqual(expect.objectContaining({
|
|
155
|
+
referenceNumber: null,
|
|
156
|
+
localReferenceNumber: 1,
|
|
157
|
+
referenceLabel: 'LT-1'
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
reloadedCore.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
it('lets the incoming synced task keep its preferred reference and renumbers the local incumbent', () => {
|
|
169
|
+
const { core, cleanup } = makeCore();
|
|
170
|
+
try {
|
|
171
|
+
const localTask = core.createTask({ title: 'Local task' }, { workspaceId: 'workspace-a' });
|
|
172
|
+
const syncResult = core.applyWorkspaceSyncSnapshot({
|
|
173
|
+
tasks: [
|
|
174
|
+
{
|
|
175
|
+
id: 'task-sync-imported-collision',
|
|
176
|
+
title: 'Remote task',
|
|
177
|
+
status: 'task',
|
|
178
|
+
referenceNumber: localTask.referenceNumber,
|
|
179
|
+
createdAt: '2026-04-10T15:00:00.000Z',
|
|
180
|
+
updatedAt: '2026-04-10T15:00:00.000Z',
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}, 'workspace-a');
|
|
184
|
+
expect(syncResult.created).toBe(1);
|
|
185
|
+
expect(core.getTask(localTask.id, 'workspace-a')?.referenceLabel).toBe('T-2');
|
|
186
|
+
expect(core.getTask('task-sync-imported-collision', 'workspace-a')?.referenceLabel).toBe('T-1');
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
cleanup();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
it('lets incoming synced updates claim the preferred reference and renumbers the displaced task', () => {
|
|
193
|
+
const { core, cleanup } = makeCore();
|
|
194
|
+
try {
|
|
195
|
+
const first = core.createTask({ title: 'First' }, { workspaceId: 'workspace-a' });
|
|
196
|
+
const second = core.createTask({ title: 'Second' }, { workspaceId: 'workspace-a' });
|
|
197
|
+
const syncResult = core.applyWorkspaceSyncSnapshot({
|
|
198
|
+
tasks: [
|
|
199
|
+
{
|
|
200
|
+
id: second.id,
|
|
201
|
+
title: 'Second',
|
|
202
|
+
status: 'task',
|
|
203
|
+
referenceNumber: first.referenceNumber,
|
|
204
|
+
updatedAt: '2099-04-11T16:00:00.000Z',
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}, 'workspace-a');
|
|
208
|
+
expect(syncResult.updated).toBe(1);
|
|
209
|
+
expect(core.getTask(first.id, 'workspace-a')?.referenceLabel).toBe('T-3');
|
|
210
|
+
expect(core.getTask(second.id, 'workspace-a')?.referenceLabel).toBe('T-1');
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
cleanup();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
83
216
|
it('resolves task identifiers by task reference or internal id', () => {
|
|
84
217
|
const { core, cleanup } = makeCore();
|
|
85
218
|
try {
|