@taskforcehq/taskforce 0.3.303 → 0.3.305
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 +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -20,100 +20,21 @@
|
|
|
20
20
|
*/
|
|
21
21
|
// === SECTION: Imports & module-level constants ===
|
|
22
22
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
23
|
-
import { ensureCloudWorkspaceReadyForSyncService,
|
|
23
|
+
import { ensureCloudWorkspaceReadyForSyncService, syncUserGlobalSettingsService } from '../sync/syncService';
|
|
24
24
|
import { buildWorkspaceSyncPayloadModel, buildWorkspaceSyncSignatureModel } from '../sync/workspaceSyncModel';
|
|
25
|
-
import { parseAiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
26
25
|
import { useWorkspaceSyncController } from './useWorkspaceSyncController';
|
|
27
26
|
import { normalizeTaskforceTheme } from '../utils/theme';
|
|
28
27
|
import { useRealtimeSync } from './useRealtimeSync';
|
|
29
28
|
import { TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT } from '../utils/contextAssetEvents';
|
|
30
29
|
import { setLocale as setAppLocale } from '../localization';
|
|
31
30
|
import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
import { createSyncAuthCoordinator } from './sync/auth';
|
|
32
|
+
import { createBootstrapTimeoutMonitor, syncBootstrapPhaseTracking } from './sync/bootstrap';
|
|
33
|
+
import { buildTaxonomyStateFingerprint, flattenDocumentReviewComments, flattenTaskEvents, isValidWorkspaceKey, logSyncDebug, normalizeWorkspaceSyncAiProfileSnapshotEntry, readPersistedSyncWatermarks, readUserGlobalSyncMeta, WORKSPACE_CONCURRENT_WINDOW_MESSAGE, WORKSPACE_FULL_RECONCILE_INTERVAL_MS, writeUserGlobalSyncMeta } from './sync/orchestratorShared';
|
|
34
|
+
import { createRealtimeSyncBridge } from './sync/realtime';
|
|
35
|
+
import { createSyncRecoveryCoordinator } from './sync/recovery';
|
|
36
|
+
import { createWorkspaceTransferCoordinator } from './sync/transfers';
|
|
38
37
|
// === SECTION: Pure utility functions ===
|
|
39
|
-
function isValidWorkspaceKey(workspaceId) {
|
|
40
|
-
const workspaceKey = String(workspaceId || '').trim();
|
|
41
|
-
return workspaceKey.length > 0 && workspaceKey.toLowerCase() !== 'default';
|
|
42
|
-
}
|
|
43
|
-
function isSyncDebugLoggingEnabled() {
|
|
44
|
-
if (typeof window === 'undefined')
|
|
45
|
-
return false;
|
|
46
|
-
try {
|
|
47
|
-
const raw = String(window.localStorage.getItem(SYNC_DEBUG_LOG_KEY) || '').trim().toLowerCase();
|
|
48
|
-
return raw === '1' || raw === 'true' || raw === 'on' || raw === 'yes';
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function logSyncDebug(event, details) {
|
|
55
|
-
if (!isSyncDebugLoggingEnabled())
|
|
56
|
-
return;
|
|
57
|
-
const payload = details && typeof details === 'object' ? details : {};
|
|
58
|
-
console.info('[Taskforce Sync]', event, payload);
|
|
59
|
-
}
|
|
60
|
-
export function normalizeWorkspaceSyncAiProfileSnapshotEntry(entry) {
|
|
61
|
-
const metadata = entry?.providerMetadata ?? entry?.provider_metadata ?? null;
|
|
62
|
-
return {
|
|
63
|
-
id: String(entry?.id || '').trim(),
|
|
64
|
-
workspaceId: String(entry?.workspaceId || entry?.workspace_id || '').trim(),
|
|
65
|
-
profileToken: String(entry?.profileToken || entry?.profile_token || '').trim(),
|
|
66
|
-
name: String(entry?.name || '').trim(),
|
|
67
|
-
username: String(entry?.username || '').trim(),
|
|
68
|
-
icon: String(entry?.icon || 'Bot').trim() || 'Bot',
|
|
69
|
-
color: String(entry?.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
70
|
-
surfaceType: parseAiProfileSurfaceType(entry?.surfaceType ?? entry?.surface_type),
|
|
71
|
-
providerMetadata: metadata && typeof metadata === 'object' && !Array.isArray(metadata)
|
|
72
|
-
? metadata
|
|
73
|
-
: null,
|
|
74
|
-
createdAt: String(entry?.createdAt || entry?.created_at || '').trim(),
|
|
75
|
-
updatedAt: String(entry?.updatedAt || entry?.updated_at || entry?.createdAt || entry?.created_at || '').trim(),
|
|
76
|
-
lastActiveAt: typeof entry?.lastActiveAt === 'string' && entry.lastActiveAt.trim().length > 0
|
|
77
|
-
? entry.lastActiveAt.trim()
|
|
78
|
-
: typeof entry?.last_active_at === 'string' && entry.last_active_at.trim().length > 0
|
|
79
|
-
? entry.last_active_at.trim()
|
|
80
|
-
: null
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function readUserGlobalSyncMeta() {
|
|
84
|
-
if (typeof window === 'undefined')
|
|
85
|
-
return {};
|
|
86
|
-
try {
|
|
87
|
-
const raw = window.localStorage.getItem(USER_GLOBAL_SYNC_META_KEY);
|
|
88
|
-
if (!raw)
|
|
89
|
-
return {};
|
|
90
|
-
const parsed = JSON.parse(raw);
|
|
91
|
-
return parsed && typeof parsed === 'object'
|
|
92
|
-
? parsed
|
|
93
|
-
: {};
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function writeUserGlobalSyncMeta(next) {
|
|
100
|
-
if (typeof window === 'undefined')
|
|
101
|
-
return;
|
|
102
|
-
try {
|
|
103
|
-
window.localStorage.setItem(USER_GLOBAL_SYNC_META_KEY, JSON.stringify(next));
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// best-effort only
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function buildTaxonomyStateFingerprint(taxonomyState) {
|
|
110
|
-
try {
|
|
111
|
-
return JSON.stringify(taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return 'null';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
38
|
function summarizeSyncPhase(enabled, phase, busy, retryAt, lastErrorMessage) {
|
|
118
39
|
if (!enabled)
|
|
119
40
|
return 'Sync is turned off for this workspace.';
|
|
@@ -156,84 +77,6 @@ function recommendSyncAction(enabled, phase, retryAt, lastErrorMessage) {
|
|
|
156
77
|
}
|
|
157
78
|
return 'No action needed.';
|
|
158
79
|
}
|
|
159
|
-
function flattenDocumentReviewComments(sessions) {
|
|
160
|
-
return sessions.flatMap((session) => {
|
|
161
|
-
const sessionId = String(session?.id || '').trim();
|
|
162
|
-
if (!sessionId || !Array.isArray(session?.comments))
|
|
163
|
-
return [];
|
|
164
|
-
return session.comments
|
|
165
|
-
.map((comment) => ({
|
|
166
|
-
id: String(comment?.id || '').trim(),
|
|
167
|
-
sessionId,
|
|
168
|
-
body: String(comment?.body || ''),
|
|
169
|
-
anchor: comment?.anchor ?? null,
|
|
170
|
-
order: Number.isFinite(Number(comment?.order)) ? Math.max(0, Math.floor(Number(comment.order))) : 0,
|
|
171
|
-
authorActorId: typeof comment?.authorActorId === 'string' && comment.authorActorId.trim().length > 0 ? comment.authorActorId.trim() : null,
|
|
172
|
-
createdAt: String(comment?.createdAt || '').trim(),
|
|
173
|
-
updatedAt: String(comment?.updatedAt || comment?.createdAt || '').trim(),
|
|
174
|
-
deletedAt: typeof comment?.deletedAt === 'string' && comment.deletedAt.trim().length > 0 ? comment.deletedAt.trim() : null,
|
|
175
|
-
}))
|
|
176
|
-
.filter((comment) => comment.id.length > 0 && comment.sessionId.length > 0);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
function readPersistedSyncWatermarks(workspaceId, taxonomyStateFingerprint) {
|
|
180
|
-
if (typeof window === 'undefined')
|
|
181
|
-
return null;
|
|
182
|
-
try {
|
|
183
|
-
const key = `${WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX}.${workspaceId}`;
|
|
184
|
-
const raw = window.localStorage.getItem(key);
|
|
185
|
-
if (!raw)
|
|
186
|
-
return null;
|
|
187
|
-
const parsed = JSON.parse(raw);
|
|
188
|
-
if (!parsed || (parsed.v !== 2 && parsed.v !== 3 && parsed.v !== 4 && parsed.v !== 5 && parsed.v !== 6 && parsed.v !== 7))
|
|
189
|
-
return null;
|
|
190
|
-
const savedAt = Date.parse(String(parsed.savedAt || ''));
|
|
191
|
-
if (!Number.isFinite(savedAt) || Date.now() - savedAt > WORKSPACE_SYNC_WATERMARKS_MAX_AGE_MS)
|
|
192
|
-
return null;
|
|
193
|
-
const savedTaxonomyStateFingerprint = typeof parsed.taxonomyStateFingerprint === 'string'
|
|
194
|
-
? parsed.taxonomyStateFingerprint
|
|
195
|
-
: '';
|
|
196
|
-
const taskWatermarks = (!taxonomyStateFingerprint || !savedTaxonomyStateFingerprint || savedTaxonomyStateFingerprint === taxonomyStateFingerprint)
|
|
197
|
-
? new Map(Array.isArray(parsed.taskWatermarks) ? parsed.taskWatermarks : [])
|
|
198
|
-
: new Map();
|
|
199
|
-
return {
|
|
200
|
-
taskWatermarks,
|
|
201
|
-
initiativeWatermarks: new Map(Array.isArray(parsed.initiativeWatermarks) ? parsed.initiativeWatermarks : []),
|
|
202
|
-
workstreamWatermarks: new Map(Array.isArray(parsed.workstreamWatermarks) ? parsed.workstreamWatermarks : []),
|
|
203
|
-
documentWatermarks: new Map(Array.isArray(parsed.documentWatermarks) ? parsed.documentWatermarks : []),
|
|
204
|
-
assetWatermarks: new Map(Array.isArray(parsed.assetWatermarks) ? parsed.assetWatermarks : []),
|
|
205
|
-
documentReviewSessionWatermarks: new Map(Array.isArray(parsed.documentReviewSessionWatermarks) ? parsed.documentReviewSessionWatermarks : []),
|
|
206
|
-
documentReviewCommentWatermarks: new Map(Array.isArray(parsed.documentReviewCommentWatermarks) ? parsed.documentReviewCommentWatermarks : []),
|
|
207
|
-
annotatedAttachmentSessionWatermarks: new Map(Array.isArray(parsed.annotatedAttachmentSessionWatermarks) ? parsed.annotatedAttachmentSessionWatermarks : []),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function savePersistedSyncWatermarks(workspaceId, data) {
|
|
215
|
-
if (typeof window === 'undefined')
|
|
216
|
-
return;
|
|
217
|
-
try {
|
|
218
|
-
const key = `${WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX}.${workspaceId}`;
|
|
219
|
-
window.localStorage.setItem(key, JSON.stringify({
|
|
220
|
-
v: 7,
|
|
221
|
-
savedAt: new Date().toISOString(),
|
|
222
|
-
taskWatermarks: Array.from(data.taskWatermarks.entries()),
|
|
223
|
-
initiativeWatermarks: Array.from(data.initiativeWatermarks.entries()),
|
|
224
|
-
workstreamWatermarks: Array.from(data.workstreamWatermarks.entries()),
|
|
225
|
-
documentWatermarks: Array.from(data.documentWatermarks.entries()),
|
|
226
|
-
assetWatermarks: Array.from(data.assetWatermarks.entries()),
|
|
227
|
-
documentReviewSessionWatermarks: Array.from(data.documentReviewSessionWatermarks.entries()),
|
|
228
|
-
documentReviewCommentWatermarks: Array.from(data.documentReviewCommentWatermarks.entries()),
|
|
229
|
-
annotatedAttachmentSessionWatermarks: Array.from(data.annotatedAttachmentSessionWatermarks.entries()),
|
|
230
|
-
taxonomyStateFingerprint: typeof data.taxonomyStateFingerprint === 'string' ? data.taxonomyStateFingerprint : ''
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
// localStorage may be full or unavailable; non-fatal
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
80
|
const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
|
|
238
81
|
documents: false,
|
|
239
82
|
aiProfiles: false,
|
|
@@ -241,8 +84,9 @@ const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
|
|
|
241
84
|
documentReviewSessions: false,
|
|
242
85
|
annotatedAttachmentSessions: false
|
|
243
86
|
};
|
|
87
|
+
export { normalizeWorkspaceSyncAiProfileSnapshotEntry };
|
|
244
88
|
export function useSyncOrchestrator(input) {
|
|
245
|
-
const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
|
|
89
|
+
const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, deletedTasks = [], initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
|
|
246
90
|
const taxonomyStateFingerprint = useMemo(() => buildTaxonomyStateFingerprint(taxonomyState), [taxonomyState]);
|
|
247
91
|
// === SECTION: State initialization ===
|
|
248
92
|
const [userGlobalSyncStatus, setUserGlobalSyncStatus] = useState('disconnected');
|
|
@@ -253,6 +97,7 @@ export function useSyncOrchestrator(input) {
|
|
|
253
97
|
const [workspaceLastErrorMessage, setWorkspaceLastErrorMessage] = useState(null);
|
|
254
98
|
const [workspaceCloudSyncEnabled, setWorkspaceCloudSyncEnabled] = useState(false);
|
|
255
99
|
const [workspaceSyncPhase, setWorkspaceSyncPhase] = useState('idle');
|
|
100
|
+
const [workspaceSyncSetupIntent, setWorkspaceSyncSetupIntent] = useState(null);
|
|
256
101
|
const [workspaceSyncBusy, setWorkspaceSyncBusy] = useState(false);
|
|
257
102
|
const [workspaceSyncPendingChanges, setWorkspaceSyncPendingChanges] = useState(0);
|
|
258
103
|
const [workspaceSyncMarkdownDocuments, setWorkspaceSyncMarkdownDocuments] = useState([]);
|
|
@@ -381,8 +226,12 @@ export function useSyncOrchestrator(input) {
|
|
|
381
226
|
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
|
|
382
227
|
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
|
|
383
228
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
|
|
229
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map();
|
|
384
230
|
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
|
|
385
231
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
|
|
232
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map();
|
|
233
|
+
workspaceStartupFullReconcileRanRef.current = false;
|
|
234
|
+
workspaceFullReconcileInFlightRef.current = false;
|
|
386
235
|
const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
|
|
387
236
|
if (persisted) {
|
|
388
237
|
workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
|
|
@@ -393,6 +242,7 @@ export function useSyncOrchestrator(input) {
|
|
|
393
242
|
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
|
|
394
243
|
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
|
|
395
244
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
|
|
245
|
+
workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
|
|
396
246
|
}
|
|
397
247
|
}, [currentWorkspaceId, taxonomyStateFingerprint]);
|
|
398
248
|
const markWorkspaceSyncReferenceSnapshotReady = useCallback((key) => {
|
|
@@ -404,14 +254,25 @@ export function useSyncOrchestrator(input) {
|
|
|
404
254
|
}));
|
|
405
255
|
}, []);
|
|
406
256
|
const workspaceSyncReferenceSnapshotsReady = Object.values(workspaceSyncReferenceSnapshotStatus).every(Boolean);
|
|
407
|
-
const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
|
|
257
|
+
const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot: applyWorkspaceSyncStateSnapshotBase, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
|
|
408
258
|
currentWorkspaceId,
|
|
409
259
|
setWorkspaceCloudSyncEnabled,
|
|
410
260
|
setWorkspaceSyncPhase,
|
|
261
|
+
setWorkspaceSyncSetupIntent,
|
|
411
262
|
setWorkspaceLastPullAt,
|
|
412
263
|
setWorkspaceLastPushAt,
|
|
413
264
|
setWorkspaceLastErrorMessage
|
|
414
265
|
});
|
|
266
|
+
const applyWorkspaceSyncStateSnapshot = useCallback((nextState) => {
|
|
267
|
+
if (nextState.phase === 'attach-cloud' || nextState.phase === 'provision-local') {
|
|
268
|
+
lastBootstrapPhaseRef.current = nextState.phase;
|
|
269
|
+
if (bootstrapPhaseStartedAtRef.current === null) {
|
|
270
|
+
bootstrapPhaseStartedAtRef.current = Date.now();
|
|
271
|
+
}
|
|
272
|
+
bootstrapLastProgressAtRef.current = null;
|
|
273
|
+
}
|
|
274
|
+
applyWorkspaceSyncStateSnapshotBase(nextState);
|
|
275
|
+
}, [applyWorkspaceSyncStateSnapshotBase]);
|
|
415
276
|
const workspaceLastPushedWatermarksRef = useRef(new Map());
|
|
416
277
|
const workspaceLastPushedInitiativeIdsRef = useRef(new Set());
|
|
417
278
|
const workspaceLastPushedInitiativeWatermarksRef = useRef(new Map());
|
|
@@ -429,12 +290,15 @@ export function useSyncOrchestrator(input) {
|
|
|
429
290
|
const workspaceLastPushedDocumentReviewCommentWatermarksRef = useRef(new Map());
|
|
430
291
|
const workspaceLastPushedAnnotatedAttachmentSessionIdsRef = useRef(new Set());
|
|
431
292
|
const workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef = useRef(new Map());
|
|
293
|
+
const workspaceLastPushedTaskEventWatermarksRef = useRef(new Map());
|
|
432
294
|
const workspaceStartupRepairRanRef = useRef(false);
|
|
295
|
+
const workspaceStartupFullReconcileRanRef = useRef(false);
|
|
433
296
|
const workspaceRepairModeRef = useRef(false);
|
|
434
297
|
const workspaceAttachBootstrapBaselineSignatureRef = useRef('');
|
|
435
298
|
const workspaceCaptureAttachBootstrapBaselineRef = useRef(false);
|
|
436
299
|
const workspaceDocumentsRefreshIntervalMs = 30_000;
|
|
437
300
|
const workspacePullIntervalMs = realtimeSyncEnabled ? 30_000 : 15_000;
|
|
301
|
+
const workspacePushWatchdogIntervalMs = Math.max(30_000, workspacePullIntervalMs);
|
|
438
302
|
const workspaceLastSuccessfulSyncAt = useMemo(() => {
|
|
439
303
|
const pullMs = workspaceLastPullAt ? Date.parse(workspaceLastPullAt) : NaN;
|
|
440
304
|
const pushMs = workspaceLastPushAt ? Date.parse(workspaceLastPushAt) : NaN;
|
|
@@ -656,51 +520,44 @@ export function useSyncOrchestrator(input) {
|
|
|
656
520
|
operation
|
|
657
521
|
});
|
|
658
522
|
}, [currentWorkspaceId]);
|
|
523
|
+
const persistWorkspaceSyncPatchBestEffort = useCallback(async (patch) => {
|
|
524
|
+
try {
|
|
525
|
+
return await persistWorkspaceSyncPatch(patch);
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return applyWorkspaceSyncPatchLocally(patch);
|
|
529
|
+
}
|
|
530
|
+
}, [applyWorkspaceSyncPatchLocally, persistWorkspaceSyncPatch]);
|
|
659
531
|
const persistWorkspaceSyncPatchSafely = useCallback((patch) => {
|
|
660
|
-
void
|
|
661
|
-
}, [
|
|
532
|
+
void persistWorkspaceSyncPatchBestEffort(patch).catch(() => { });
|
|
533
|
+
}, [persistWorkspaceSyncPatchBestEffort]);
|
|
662
534
|
const scheduleWorkspaceSyncRetry = useCallback((minDelayMs) => {
|
|
663
535
|
scheduleWorkspaceRetry(() => retryWorkspaceSyncRef.current(), { minDelayMs });
|
|
664
536
|
}, [scheduleWorkspaceRetry]);
|
|
665
537
|
useEffect(() => {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
bootstrapPhaseStartedAtRef.current = null;
|
|
675
|
-
bootstrapLastProgressAtRef.current = null;
|
|
538
|
+
syncBootstrapPhaseTracking({
|
|
539
|
+
workspaceSyncPhase,
|
|
540
|
+
lastBootstrapPhaseRef,
|
|
541
|
+
bootstrapPhaseStartedAtRef,
|
|
542
|
+
bootstrapLastProgressAtRef
|
|
543
|
+
});
|
|
676
544
|
}, [workspaceSyncPhase]);
|
|
677
545
|
useEffect(() => {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
694
|
-
setUserGlobalSyncStatus('error');
|
|
695
|
-
setUserGlobalSyncError(message);
|
|
696
|
-
setWorkspaceSyncBusy(false);
|
|
697
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
698
|
-
logSyncDebug('workspace_sync_bootstrap_timeout', {
|
|
699
|
-
workspaceId: currentWorkspaceId,
|
|
700
|
-
phase: workspaceSyncPhase
|
|
701
|
-
});
|
|
702
|
-
}, 10_000);
|
|
703
|
-
return () => window.clearInterval(intervalId);
|
|
546
|
+
return createBootstrapTimeoutMonitor({
|
|
547
|
+
currentWorkspaceId,
|
|
548
|
+
workspaceCloudSyncEnabled,
|
|
549
|
+
workspaceSyncPhase,
|
|
550
|
+
workspacePullInFlightRef,
|
|
551
|
+
workspacePushInFlightRef,
|
|
552
|
+
bootstrapPhaseStartedAtRef,
|
|
553
|
+
bootstrapLastProgressAtRef,
|
|
554
|
+
setWorkspaceLastErrorMessage: (value) => setWorkspaceLastErrorMessage(value),
|
|
555
|
+
setWorkspaceLastErrorAt: (value) => setWorkspaceLastErrorAt(value),
|
|
556
|
+
setUserGlobalSyncStatus,
|
|
557
|
+
setUserGlobalSyncError: (value) => setUserGlobalSyncError(value),
|
|
558
|
+
setWorkspaceSyncBusy,
|
|
559
|
+
persistWorkspaceSyncPatchSafely
|
|
560
|
+
});
|
|
704
561
|
}, [
|
|
705
562
|
currentWorkspaceId,
|
|
706
563
|
workspaceCloudSyncEnabled,
|
|
@@ -716,7 +573,8 @@ export function useSyncOrchestrator(input) {
|
|
|
716
573
|
isAuthenticated,
|
|
717
574
|
resolveCloudAuthUrl,
|
|
718
575
|
workspaceId: currentWorkspaceId,
|
|
719
|
-
workspaceName: projectName || currentWorkspaceId
|
|
576
|
+
workspaceName: projectName || currentWorkspaceId,
|
|
577
|
+
allowProvisionOnMismatch: workspaceSyncPhase !== 'attach-cloud'
|
|
720
578
|
});
|
|
721
579
|
}, [
|
|
722
580
|
cloudAuthConfigured,
|
|
@@ -724,7 +582,8 @@ export function useSyncOrchestrator(input) {
|
|
|
724
582
|
isAuthenticated,
|
|
725
583
|
resolveCloudAuthUrl,
|
|
726
584
|
currentWorkspaceId,
|
|
727
|
-
projectName
|
|
585
|
+
projectName,
|
|
586
|
+
workspaceSyncPhase
|
|
728
587
|
]);
|
|
729
588
|
useEffect(() => {
|
|
730
589
|
workspaceTasksRef.current = tasks || [];
|
|
@@ -732,6 +591,14 @@ export function useSyncOrchestrator(input) {
|
|
|
732
591
|
useEffect(() => {
|
|
733
592
|
workspaceArchivedTasksRef.current = archivedTasks || [];
|
|
734
593
|
}, [archivedTasks]);
|
|
594
|
+
useEffect(() => {
|
|
595
|
+
workspaceDeletedTaskWatermarksRef.current = new Map((deletedTasks || [])
|
|
596
|
+
.map((entry) => [
|
|
597
|
+
String(entry?.taskId || '').trim(),
|
|
598
|
+
String(entry?.deletedAt || '').trim()
|
|
599
|
+
])
|
|
600
|
+
.filter(([taskId, deletedAt]) => taskId.length > 0 && Number.isFinite(Date.parse(deletedAt))));
|
|
601
|
+
}, [deletedTasks, workspaceDeletedTaskWatermarksRef]);
|
|
735
602
|
useEffect(() => {
|
|
736
603
|
workspaceInitiativesRef.current = initiatives || [];
|
|
737
604
|
}, [initiatives]);
|
|
@@ -739,6 +606,7 @@ export function useSyncOrchestrator(input) {
|
|
|
739
606
|
workspaceWorkstreamsRef.current = workstreams || [];
|
|
740
607
|
}, [workstreams]);
|
|
741
608
|
const workspaceForceFullAiProfilePushRef = useRef(false);
|
|
609
|
+
const workspaceFullReconcileInFlightRef = useRef(false);
|
|
742
610
|
const buildWorkspaceSyncPayload = useCallback((options) => {
|
|
743
611
|
const forceAllAiProfiles = options?.forceAllAiProfiles === true;
|
|
744
612
|
// Read documents and assets from refs rather than state so that the freshest data
|
|
@@ -747,8 +615,10 @@ export function useSyncOrchestrator(input) {
|
|
|
747
615
|
return buildWorkspaceSyncPayloadModel({
|
|
748
616
|
tasks: workspaceTasksRef.current,
|
|
749
617
|
archivedTasks: workspaceArchivedTasksRef.current,
|
|
618
|
+
taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
|
|
750
619
|
lastPushedTaskIds: workspaceLastPushedTaskIdsRef.current,
|
|
751
620
|
pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
|
|
621
|
+
pendingDeletedTaskWatermarks: workspaceDeletedTaskWatermarksRef.current,
|
|
752
622
|
initiatives: workspaceInitiativesRef.current,
|
|
753
623
|
workstreams: workspaceWorkstreamsRef.current,
|
|
754
624
|
taxonomies: taxonomies || [],
|
|
@@ -775,6 +645,7 @@ export function useSyncOrchestrator(input) {
|
|
|
775
645
|
lastPushedDocumentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
|
|
776
646
|
lastPushedAnnotatedAttachmentSessionIds: workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current,
|
|
777
647
|
lastPushedAnnotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
|
|
648
|
+
lastPushedTaskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
|
|
778
649
|
lastPushedWatermarks: workspaceLastPushedWatermarksRef.current
|
|
779
650
|
});
|
|
780
651
|
}, [taxonomies, taxonomyState]);
|
|
@@ -802,6 +673,7 @@ export function useSyncOrchestrator(input) {
|
|
|
802
673
|
workspaceId: currentWorkspaceId,
|
|
803
674
|
tasks: workspaceTasksRef.current,
|
|
804
675
|
archivedTasks: workspaceArchivedTasksRef.current,
|
|
676
|
+
taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
|
|
805
677
|
pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
|
|
806
678
|
initiatives: workspaceInitiativesRef.current,
|
|
807
679
|
workstreams: workspaceWorkstreamsRef.current,
|
|
@@ -813,7 +685,52 @@ export function useSyncOrchestrator(input) {
|
|
|
813
685
|
documentReviewComments: workspaceSyncDocumentReviewCommentsRef.current,
|
|
814
686
|
annotatedAttachmentSessions: workspaceSyncAnnotatedAttachmentSessionsRef.current
|
|
815
687
|
});
|
|
816
|
-
}, [currentWorkspaceId, taxonomies]);
|
|
688
|
+
}, [currentWorkspaceId, taxonomies, taxonomyState]);
|
|
689
|
+
const workspaceCoreSyncFingerprint = useMemo(() => JSON.stringify({
|
|
690
|
+
tasks: (tasks || [])
|
|
691
|
+
.map((task) => ({
|
|
692
|
+
id: String(task.id || '').trim(),
|
|
693
|
+
updatedAt: String(task.updatedAt || task.createdAt || '').trim(),
|
|
694
|
+
status: String(task.status || '').trim(),
|
|
695
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
696
|
+
localReferenceNumber: task.localReferenceNumber ?? null
|
|
697
|
+
}))
|
|
698
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
699
|
+
archivedTasks: (archivedTasks || [])
|
|
700
|
+
.map((task) => ({
|
|
701
|
+
id: String(task.id || '').trim(),
|
|
702
|
+
updatedAt: String(task.updatedAt || task.createdAt || '').trim(),
|
|
703
|
+
status: String(task.status || '').trim(),
|
|
704
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
705
|
+
localReferenceNumber: task.localReferenceNumber ?? null
|
|
706
|
+
}))
|
|
707
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
708
|
+
initiatives: (initiatives || [])
|
|
709
|
+
.map((initiative) => ({
|
|
710
|
+
id: String(initiative.id || '').trim(),
|
|
711
|
+
updatedAt: String(initiative.updatedAt || initiative.createdAt || '').trim(),
|
|
712
|
+
isArchived: Boolean(initiative.isArchived)
|
|
713
|
+
}))
|
|
714
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
715
|
+
workstreams: (workstreams || [])
|
|
716
|
+
.map((workstream) => ({
|
|
717
|
+
id: String(workstream.id || '').trim(),
|
|
718
|
+
updatedAt: String(workstream.updatedAt || workstream.createdAt || '').trim(),
|
|
719
|
+
initiativeId: typeof workstream.initiativeId === 'string' ? workstream.initiativeId.trim() : '',
|
|
720
|
+
isArchived: Boolean(workstream.isArchived)
|
|
721
|
+
}))
|
|
722
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
723
|
+
taskEvents: flattenTaskEvents(tasks || [], archivedTasks || [])
|
|
724
|
+
.map((event) => ({
|
|
725
|
+
id: String(event.id || '').trim(),
|
|
726
|
+
taskId: String(event.taskId || '').trim(),
|
|
727
|
+
createdAt: String(event.createdAt || '').trim(),
|
|
728
|
+
action: String(event.action || '').trim(),
|
|
729
|
+
}))
|
|
730
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
731
|
+
taxonomies: Array.isArray(taxonomies) ? taxonomies : [],
|
|
732
|
+
taxonomyState: taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null
|
|
733
|
+
}), [tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
|
|
817
734
|
// Fingerprint of per-task attachment counts — changes when the MCP tool saves a new
|
|
818
735
|
// attachment and the React app polls the updated task. Used to trigger an immediate
|
|
819
736
|
// asset snapshot refresh without requiring a cross-process DOM event.
|
|
@@ -1172,6 +1089,9 @@ export function useSyncOrchestrator(input) {
|
|
|
1172
1089
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(workspaceSyncAnnotatedAttachmentSessionsRef.current
|
|
1173
1090
|
.map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
|
|
1174
1091
|
.filter(([id]) => id.length > 0));
|
|
1092
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current)
|
|
1093
|
+
.map((entry) => [String(entry?.id || '').trim(), String(entry?.createdAt || '').trim()])
|
|
1094
|
+
.filter(([id]) => id.length > 0));
|
|
1175
1095
|
}, []);
|
|
1176
1096
|
const refreshLocalWorkspaceTaskCollections = useCallback(async () => {
|
|
1177
1097
|
const [tasksRes, archiveRes] = await Promise.all([
|
|
@@ -1208,557 +1128,174 @@ export function useSyncOrchestrator(input) {
|
|
|
1208
1128
|
});
|
|
1209
1129
|
}, [currentWorkspaceId, fetchPlanningEntities, setArchivedTasks, setTasks]);
|
|
1210
1130
|
// === SECTION: Auth failure handling & sync readiness ===
|
|
1211
|
-
const handleSyncAuthFailure =
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
setUserGlobalSyncStatus('error');
|
|
1215
|
-
const message = 'Session expired. Sign in again to resume cloud sync.';
|
|
1216
|
-
setUserGlobalSyncError(message);
|
|
1217
|
-
setWorkspaceLastErrorMessage(message);
|
|
1218
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1219
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1220
|
-
eventType: source === 'handshake' ? 'handshake' : source,
|
|
1221
|
-
status: 'error',
|
|
1222
|
-
statusCode,
|
|
1223
|
-
errorMessage: message
|
|
1224
|
-
});
|
|
1225
|
-
setAuthBlocked(true);
|
|
1226
|
-
setIsAuthenticated(false);
|
|
1227
|
-
await checkAuthSession();
|
|
1228
|
-
}, [
|
|
1131
|
+
const { handleSyncAuthFailure, ensureWorkspaceSyncReady } = useMemo(() => createSyncAuthCoordinator({
|
|
1132
|
+
currentWorkspaceId,
|
|
1133
|
+
workspaceSyncPhase,
|
|
1229
1134
|
clearWorkspaceRetry,
|
|
1135
|
+
setWorkspaceSyncBusy,
|
|
1136
|
+
setUserGlobalSyncStatus,
|
|
1137
|
+
setUserGlobalSyncError: (value) => setUserGlobalSyncError(value),
|
|
1138
|
+
setWorkspaceLastErrorMessage: (value) => setWorkspaceLastErrorMessage(value),
|
|
1139
|
+
setWorkspaceLastErrorAt: (value) => setWorkspaceLastErrorAt(value),
|
|
1230
1140
|
reportSyncEvent,
|
|
1231
|
-
currentWorkspaceId,
|
|
1232
1141
|
setAuthBlocked,
|
|
1233
1142
|
setIsAuthenticated,
|
|
1234
|
-
checkAuthSession
|
|
1235
|
-
]);
|
|
1236
|
-
const ensureWorkspaceSyncReady = useCallback(async () => {
|
|
1237
|
-
if (!isValidWorkspaceKey(currentWorkspaceId)) {
|
|
1238
|
-
const message = 'Workspace sync blocked: local workspace ID is unresolved.';
|
|
1239
|
-
setWorkspaceLastErrorMessage(message);
|
|
1240
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1241
|
-
setUserGlobalSyncError(message);
|
|
1242
|
-
setUserGlobalSyncStatus('error');
|
|
1243
|
-
return false;
|
|
1244
|
-
}
|
|
1245
|
-
const ensured = await ensureCloudWorkspaceReadyForSync();
|
|
1246
|
-
if (ensured.success)
|
|
1247
|
-
return true;
|
|
1248
|
-
if (ensured.statusCode === 401) {
|
|
1249
|
-
await handleSyncAuthFailure('handshake', 401);
|
|
1250
|
-
return false;
|
|
1251
|
-
}
|
|
1252
|
-
const message = ensured.error || 'Workspace sync handshake failed.';
|
|
1253
|
-
setWorkspaceLastErrorMessage(message);
|
|
1254
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1255
|
-
setUserGlobalSyncError(message);
|
|
1256
|
-
setUserGlobalSyncStatus('error');
|
|
1257
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1258
|
-
eventType: 'handshake',
|
|
1259
|
-
status: 'error',
|
|
1260
|
-
statusCode: ensured.statusCode,
|
|
1261
|
-
errorMessage: message
|
|
1262
|
-
});
|
|
1263
|
-
if (ensured.transient || isTransientWorkspaceSyncStatus(ensured.statusCode)) {
|
|
1264
|
-
scheduleWorkspaceSyncRetry(ensured.retryAfterMs);
|
|
1265
|
-
}
|
|
1266
|
-
else if (workspaceSyncPhase === 'active') {
|
|
1267
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1268
|
-
}
|
|
1269
|
-
return false;
|
|
1270
|
-
}, [
|
|
1271
|
-
currentWorkspaceId,
|
|
1143
|
+
checkAuthSession,
|
|
1272
1144
|
ensureCloudWorkspaceReadyForSync,
|
|
1273
|
-
handleSyncAuthFailure,
|
|
1274
|
-
reportSyncEvent,
|
|
1275
1145
|
scheduleWorkspaceSyncRetry,
|
|
1146
|
+
persistWorkspaceSyncPatchSafely
|
|
1147
|
+
}), [
|
|
1148
|
+
currentWorkspaceId,
|
|
1276
1149
|
workspaceSyncPhase,
|
|
1150
|
+
clearWorkspaceRetry,
|
|
1151
|
+
reportSyncEvent,
|
|
1152
|
+
setAuthBlocked,
|
|
1153
|
+
setIsAuthenticated,
|
|
1154
|
+
checkAuthSession,
|
|
1155
|
+
ensureCloudWorkspaceReadyForSync,
|
|
1156
|
+
scheduleWorkspaceSyncRetry,
|
|
1277
1157
|
persistWorkspaceSyncPatchSafely
|
|
1278
1158
|
]);
|
|
1279
|
-
// === SECTION: Push sync ===
|
|
1280
|
-
const
|
|
1281
|
-
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
1282
|
-
return false;
|
|
1283
|
-
if (!isValidWorkspaceKey(currentWorkspaceId))
|
|
1284
|
-
return false;
|
|
1285
|
-
if (workspaceRepairModeRef.current === true && options?.repairMode !== true) {
|
|
1286
|
-
workspacePendingSignatureRef.current = signature;
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1290
|
-
workspacePendingSignatureRef.current = signature;
|
|
1291
|
-
return false;
|
|
1292
|
-
}
|
|
1293
|
-
if (!isAuthenticated) {
|
|
1294
|
-
const authed = await checkAuthSession();
|
|
1295
|
-
if (!authed)
|
|
1296
|
-
return false;
|
|
1297
|
-
}
|
|
1298
|
-
if (shouldSuppressAttachBootstrapPush(signature)) {
|
|
1299
|
-
return false;
|
|
1300
|
-
}
|
|
1301
|
-
if (workspacePushInFlightRef.current || workspacePullInFlightRef.current) {
|
|
1302
|
-
workspacePendingSignatureRef.current = signature;
|
|
1303
|
-
return false;
|
|
1304
|
-
}
|
|
1305
|
-
const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'push');
|
|
1306
|
-
if (!leaseAcquired) {
|
|
1307
|
-
workspacePendingSignatureRef.current = signature;
|
|
1308
|
-
reportWorkspaceWindowContention('push');
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
workspacePushInFlightRef.current = true;
|
|
1312
|
-
setWorkspaceSyncBusy(true);
|
|
1313
|
-
setUserGlobalSyncStatus('syncing');
|
|
1314
|
-
setUserGlobalSyncError(null);
|
|
1315
|
-
const pushStartedAtMs = Date.now();
|
|
1316
|
-
const forceAllAiProfiles = options?.forceAllAiProfiles === true || workspaceForceFullAiProfilePushRef.current === true;
|
|
1317
|
-
try {
|
|
1318
|
-
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1319
|
-
await refreshWorkspaceSyncAiProfilesSnapshot();
|
|
1320
|
-
await refreshWorkspaceSyncAssetsSnapshot();
|
|
1321
|
-
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
1322
|
-
const ready = await ensureWorkspaceSyncReady();
|
|
1323
|
-
if (!ready)
|
|
1324
|
-
return false;
|
|
1325
|
-
const payload = buildWorkspaceSyncPayload({ forceAllAiProfiles });
|
|
1326
|
-
setWorkspaceSyncPendingChanges(payload.changes.length);
|
|
1327
|
-
const pushResult = await runWorkspacePushSyncService({
|
|
1328
|
-
resolveCloudAuthUrl,
|
|
1329
|
-
workspaceId: currentWorkspaceId,
|
|
1330
|
-
payload,
|
|
1331
|
-
ensureCloudWorkspaceReadyForSync,
|
|
1332
|
-
repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true
|
|
1333
|
-
});
|
|
1334
|
-
if (!pushResult.success) {
|
|
1335
|
-
if (pushResult.status === 401) {
|
|
1336
|
-
await handleSyncAuthFailure('push', 401);
|
|
1337
|
-
return false;
|
|
1338
|
-
}
|
|
1339
|
-
let message = pushResult.error
|
|
1340
|
-
? `Workspace sync push failed (${pushResult.status || 0}): ${pushResult.error}`
|
|
1341
|
-
: `Workspace sync push failed (${pushResult.status || 0})`;
|
|
1342
|
-
if (pushResult.status === 413) {
|
|
1343
|
-
message = 'Workspace sync failed because the payload is too large. This usually happens when one or more attachments exceed the project limit.';
|
|
1344
|
-
}
|
|
1345
|
-
setUserGlobalSyncStatus('error');
|
|
1346
|
-
setUserGlobalSyncError(message);
|
|
1347
|
-
setWorkspaceLastErrorMessage(message);
|
|
1348
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1349
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1350
|
-
eventType: 'push',
|
|
1351
|
-
status: 'error',
|
|
1352
|
-
statusCode: pushResult.status,
|
|
1353
|
-
errorMessage: message,
|
|
1354
|
-
requestMs: pushResult.requestMs
|
|
1355
|
-
});
|
|
1356
|
-
workspacePendingSignatureRef.current = signature;
|
|
1357
|
-
if (pushResult.transient || isTransientWorkspaceSyncStatus(pushResult.status)) {
|
|
1358
|
-
scheduleWorkspaceSyncRetry(pushResult.retryAfterMs);
|
|
1359
|
-
}
|
|
1360
|
-
else if (workspaceSyncPhase === 'active') {
|
|
1361
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1362
|
-
}
|
|
1363
|
-
return false;
|
|
1364
|
-
}
|
|
1365
|
-
workspaceLastPushedSignatureRef.current = signature;
|
|
1366
|
-
workspaceLastPushedTaskIdsRef.current = pushResult.currentTaskIds;
|
|
1367
|
-
workspaceLastPushedInitiativeIdsRef.current = new Set(pushResult.currentInitiativeIds);
|
|
1368
|
-
workspaceLastPushedInitiativeWatermarksRef.current = new Map(pushResult.currentInitiativeWatermarks);
|
|
1369
|
-
workspaceLastPushedWorkstreamIdsRef.current = new Set(pushResult.currentWorkstreamIds);
|
|
1370
|
-
workspaceLastPushedWorkstreamWatermarksRef.current = new Map(pushResult.currentWorkstreamWatermarks);
|
|
1371
|
-
workspaceLastPushedAiProfileIdsRef.current = new Set(pushResult.currentAiProfileIds);
|
|
1372
|
-
workspaceLastPushedAiProfileWatermarksRef.current = new Map(pushResult.currentAiProfileWatermarks);
|
|
1373
|
-
workspaceLastPushedDocumentPathsRef.current = new Set(pushResult.currentDocumentPaths);
|
|
1374
|
-
workspaceLastPushedDocumentWatermarksRef.current = new Map(pushResult.currentDocumentWatermarks);
|
|
1375
|
-
workspaceLastPushedAssetPathsRef.current = new Set(pushResult.currentAssetPaths);
|
|
1376
|
-
workspaceLastPushedAssetWatermarksRef.current = new Map(pushResult.currentAssetWatermarks);
|
|
1377
|
-
workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set(pushResult.currentDocumentReviewSessionIds);
|
|
1378
|
-
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map(pushResult.currentDocumentReviewSessionWatermarks);
|
|
1379
|
-
workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set(pushResult.currentDocumentReviewCommentIds);
|
|
1380
|
-
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map(pushResult.currentDocumentReviewCommentWatermarks);
|
|
1381
|
-
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set(pushResult.currentAnnotatedAttachmentSessionIds);
|
|
1382
|
-
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(pushResult.currentAnnotatedAttachmentSessionWatermarks);
|
|
1383
|
-
if (forceAllAiProfiles) {
|
|
1384
|
-
workspaceForceFullAiProfilePushRef.current = false;
|
|
1385
|
-
}
|
|
1386
|
-
for (const [taskId, watermark] of pushResult.pushedWatermarks) {
|
|
1387
|
-
workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
|
|
1388
|
-
}
|
|
1389
|
-
if (pushResult.deleteTaskIds.size > 0) {
|
|
1390
|
-
for (const taskId of pushResult.deleteTaskIds) {
|
|
1391
|
-
workspaceDeletedTaskIdsRef.current.delete(taskId);
|
|
1392
|
-
workspaceLastPushedWatermarksRef.current.delete(taskId);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
// Persist watermarks so a page reload doesn't trigger a full resync
|
|
1396
|
-
savePersistedSyncWatermarks(currentWorkspaceId, {
|
|
1397
|
-
taskWatermarks: workspaceLastPushedWatermarksRef.current,
|
|
1398
|
-
initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
|
|
1399
|
-
workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
|
|
1400
|
-
documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
|
|
1401
|
-
assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
|
|
1402
|
-
documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
|
|
1403
|
-
documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
|
|
1404
|
-
annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
|
|
1405
|
-
taxonomyStateFingerprint
|
|
1406
|
-
});
|
|
1407
|
-
if (Array.isArray(pushResult.emittedEventIds) && pushResult.emittedEventIds.length > 0) {
|
|
1408
|
-
for (const rawEventId of pushResult.emittedEventIds) {
|
|
1409
|
-
const eventId = String(rawEventId || '').trim();
|
|
1410
|
-
if (!eventId || realtimeSuppressedEventIdsRef.current.has(eventId))
|
|
1411
|
-
continue;
|
|
1412
|
-
realtimeSuppressedEventIdsRef.current.add(eventId);
|
|
1413
|
-
realtimeSuppressedEventQueueRef.current.push(eventId);
|
|
1414
|
-
}
|
|
1415
|
-
while (realtimeSuppressedEventQueueRef.current.length > 2048) {
|
|
1416
|
-
const stale = realtimeSuppressedEventQueueRef.current.shift();
|
|
1417
|
-
if (stale)
|
|
1418
|
-
realtimeSuppressedEventIdsRef.current.delete(stale);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
clearWorkspaceRetry();
|
|
1422
|
-
clearWorkspaceIssue();
|
|
1423
|
-
const syncedAt = pushResult.syncedAt || new Date().toISOString();
|
|
1424
|
-
setWorkspaceLastPushAt(syncedAt);
|
|
1425
|
-
setWorkspaceSyncPendingChanges(0);
|
|
1426
|
-
setUserGlobalSyncStatus('idle');
|
|
1427
|
-
persistWorkspaceSyncPatchSafely({
|
|
1428
|
-
phase: 'active',
|
|
1429
|
-
lastPushAt: syncedAt,
|
|
1430
|
-
lastSyncedAt: syncedAt,
|
|
1431
|
-
lastErrorMessage: null
|
|
1432
|
-
});
|
|
1433
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1434
|
-
eventType: workspaceSyncPhase === 'provision-local' ? 'bootstrap' : 'push',
|
|
1435
|
-
status: 'success',
|
|
1436
|
-
changeCount: pushResult.changeCount,
|
|
1437
|
-
requestMs: pushResult.requestMs
|
|
1438
|
-
});
|
|
1439
|
-
return true;
|
|
1440
|
-
}
|
|
1441
|
-
catch (error) {
|
|
1442
|
-
const message = 'Workspace sync push failed.';
|
|
1443
|
-
setUserGlobalSyncStatus('error');
|
|
1444
|
-
setUserGlobalSyncError(message);
|
|
1445
|
-
setWorkspaceLastErrorMessage(message);
|
|
1446
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1447
|
-
workspacePendingSignatureRef.current = signature;
|
|
1448
|
-
scheduleWorkspaceSyncRetry();
|
|
1449
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1450
|
-
eventType: 'push',
|
|
1451
|
-
status: 'error',
|
|
1452
|
-
errorMessage: `${message} ${String(error?.message || '').trim()}`.trim()
|
|
1453
|
-
});
|
|
1454
|
-
logSyncDebug('push_failed_exception', {
|
|
1455
|
-
workspaceId: currentWorkspaceId,
|
|
1456
|
-
elapsedMs: Date.now() - pushStartedAtMs
|
|
1457
|
-
});
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
finally {
|
|
1461
|
-
workspacePushInFlightRef.current = false;
|
|
1462
|
-
setWorkspaceSyncBusy(workspacePullInFlightRef.current);
|
|
1463
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1464
|
-
const pendingSignature = workspacePendingSignatureRef.current;
|
|
1465
|
-
if (pendingSignature
|
|
1466
|
-
&& pendingSignature !== workspaceLastPushedSignatureRef.current
|
|
1467
|
-
&& !workspacePullInFlightRef.current) {
|
|
1468
|
-
workspacePendingSignatureRef.current = '';
|
|
1469
|
-
window.setTimeout(() => {
|
|
1470
|
-
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
1471
|
-
}, 120);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
}, [
|
|
1159
|
+
// === SECTION: Push/Pull sync ===
|
|
1160
|
+
const transferCoordinator = useMemo(() => createWorkspaceTransferCoordinator({
|
|
1475
1161
|
cloudAuthConfigured,
|
|
1476
1162
|
runtimeMode,
|
|
1477
1163
|
workspaceCloudSyncEnabled,
|
|
1478
1164
|
currentWorkspaceId,
|
|
1479
1165
|
workspaceSyncPhase,
|
|
1166
|
+
taxonomyStateFingerprint,
|
|
1480
1167
|
isAuthenticated,
|
|
1481
1168
|
checkAuthSession,
|
|
1482
|
-
clearWorkspaceIssue,
|
|
1483
|
-
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1484
|
-
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1485
|
-
refreshWorkspaceSyncAssetsSnapshot,
|
|
1486
|
-
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1487
|
-
ensureWorkspaceSyncReady,
|
|
1488
|
-
buildWorkspaceSyncPayload,
|
|
1489
1169
|
resolveCloudAuthUrl,
|
|
1490
1170
|
ensureCloudWorkspaceReadyForSync,
|
|
1491
|
-
|
|
1171
|
+
ensureWorkspaceSyncReady,
|
|
1492
1172
|
handleSyncAuthFailure,
|
|
1493
1173
|
clearWorkspaceRetry,
|
|
1494
|
-
|
|
1495
|
-
|
|
1174
|
+
clearWorkspaceIssue,
|
|
1175
|
+
isWorkspaceRetryPending,
|
|
1496
1176
|
scheduleWorkspaceSyncRetry,
|
|
1177
|
+
persistWorkspaceSyncPatchSafely,
|
|
1178
|
+
persistWorkspaceSyncPatchBestEffort,
|
|
1179
|
+
reportSyncEvent,
|
|
1497
1180
|
acquireWorkspaceSyncLease,
|
|
1498
1181
|
releaseWorkspaceSyncLease,
|
|
1499
1182
|
reportWorkspaceWindowContention,
|
|
1183
|
+
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1184
|
+
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1185
|
+
refreshWorkspaceSyncAssetsSnapshot,
|
|
1186
|
+
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1187
|
+
refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
|
|
1188
|
+
refreshLocalWorkspaceTaskCollections,
|
|
1189
|
+
buildWorkspaceSyncPayload,
|
|
1190
|
+
buildWorkspaceSyncSignature,
|
|
1191
|
+
seedWorkspaceSnapshotPushBaseline,
|
|
1192
|
+
shouldSuppressAttachBootstrapPush,
|
|
1193
|
+
recordSuppressedEventIds: (...args) => realtimeBridgeRef.current.recordSuppressedEventIds(...args),
|
|
1194
|
+
workspaceRepairModeRef,
|
|
1195
|
+
workspaceForceFullAiProfilePushRef,
|
|
1500
1196
|
workspacePushInFlightRef,
|
|
1501
1197
|
workspacePullInFlightRef,
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
cursor,
|
|
1543
|
-
bootstrap,
|
|
1544
|
-
ensureCloudWorkspaceReadyForSync,
|
|
1545
|
-
maxPages: 20,
|
|
1546
|
-
repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true,
|
|
1547
|
-
onPagePulled: () => { bootstrapLastProgressAtRef.current = Date.now(); },
|
|
1548
|
-
onChangesApplied: () => { bootstrapLastProgressAtRef.current = Date.now(); }
|
|
1549
|
-
});
|
|
1550
|
-
if (!pullResult.success) {
|
|
1551
|
-
if (pullResult.kind === 'pull_http' && pullResult.status === 401) {
|
|
1552
|
-
await handleSyncAuthFailure('pull', 401);
|
|
1553
|
-
return false;
|
|
1554
|
-
}
|
|
1555
|
-
if (pullResult.kind === 'apply_auth') {
|
|
1556
|
-
await handleSyncAuthFailure('apply', 401);
|
|
1557
|
-
return false;
|
|
1558
|
-
}
|
|
1559
|
-
let message = pullResult.kind === 'apply_payload' || pullResult.kind === 'apply_all_candidates' || pullResult.kind === 'exception'
|
|
1560
|
-
? String(pullResult.error || 'Workspace sync apply failed.')
|
|
1561
|
-
: `Workspace sync pull failed (${pullResult.status || 0})`;
|
|
1562
|
-
if (pullResult.status === 413) {
|
|
1563
|
-
message = 'Local sync failed because the downloaded payload is too large. This usually happens when the workspace contains massive attachments.';
|
|
1564
|
-
}
|
|
1565
|
-
setUserGlobalSyncStatus('error');
|
|
1566
|
-
setUserGlobalSyncError(message);
|
|
1567
|
-
setWorkspaceLastErrorMessage(message);
|
|
1568
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1569
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1570
|
-
eventType: pullResult.kind?.startsWith('apply') ? 'apply' : 'pull',
|
|
1571
|
-
status: 'error',
|
|
1572
|
-
statusCode: pullResult.status,
|
|
1573
|
-
errorMessage: message,
|
|
1574
|
-
requestMs: pullResult.pullRequestMs,
|
|
1575
|
-
details: {
|
|
1576
|
-
pages: pullResult.pages,
|
|
1577
|
-
applyMs: pullResult.applyMs,
|
|
1578
|
-
...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
|
|
1579
|
-
}
|
|
1580
|
-
});
|
|
1581
|
-
if (pullResult.transient || pullResult.hasTransientApplyFailure || isTransientWorkspaceSyncStatus(pullResult.status)) {
|
|
1582
|
-
scheduleWorkspaceSyncRetry(pullResult.retryAfterMs);
|
|
1583
|
-
}
|
|
1584
|
-
else {
|
|
1585
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1586
|
-
}
|
|
1587
|
-
return false;
|
|
1588
|
-
}
|
|
1589
|
-
if (Array.isArray(pullResult.emittedEventIds)) {
|
|
1590
|
-
for (const eventId of pullResult.emittedEventIds) {
|
|
1591
|
-
if (eventId) {
|
|
1592
|
-
realtimeSuppressedEventIdsRef.current.add(eventId);
|
|
1593
|
-
// Optional: cleanup after a generous delay if WebSocket never arrives to clear it
|
|
1594
|
-
setTimeout(() => {
|
|
1595
|
-
realtimeSuppressedEventIdsRef.current.delete(eventId);
|
|
1596
|
-
}, 60000);
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
if (pullResult.pulledDeleteTaskIds.size > 0) {
|
|
1601
|
-
setTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
1602
|
-
setArchivedTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
1603
|
-
}
|
|
1604
|
-
const decryptFailures = Array.isArray(pullResult.documentDecryptFailures)
|
|
1605
|
-
? pullResult.documentDecryptFailures.filter((value) => String(value || '').trim().length > 0)
|
|
1606
|
-
: [];
|
|
1607
|
-
if (decryptFailures.length > 0) {
|
|
1608
|
-
logSyncDebug('pull_document_decrypt_failures', {
|
|
1609
|
-
workspaceId: currentWorkspaceId,
|
|
1610
|
-
failureCount: decryptFailures.length
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
workspacePullCursorRef.current = pullResult.cursor || null;
|
|
1614
|
-
if (pullResult.appliedChanges > 0 && (pullResult.pulledActiveUpserts || pullResult.pulledArchivedUpserts)) {
|
|
1615
|
-
try {
|
|
1616
|
-
await refreshLocalWorkspaceTaskCollections();
|
|
1617
|
-
}
|
|
1618
|
-
catch {
|
|
1619
|
-
logSyncDebug('pull_local_refresh_failed', {
|
|
1620
|
-
workspaceId: currentWorkspaceId,
|
|
1621
|
-
appliedChanges: pullResult.appliedChanges
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
clearWorkspaceRetry();
|
|
1626
|
-
clearWorkspaceIssue();
|
|
1627
|
-
const syncedAt = pullResult.syncedAt || new Date().toISOString();
|
|
1628
|
-
setWorkspaceLastPullAt(syncedAt);
|
|
1629
|
-
setWorkspaceSyncPendingChanges(0);
|
|
1630
|
-
setUserGlobalSyncStatus('idle');
|
|
1631
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1632
|
-
workspaceCaptureAttachBootstrapBaselineRef.current = true;
|
|
1633
|
-
workspacePendingSignatureRef.current = '';
|
|
1634
|
-
}
|
|
1635
|
-
await persistWorkspaceSyncPatch({
|
|
1636
|
-
phase: 'active',
|
|
1637
|
-
pullCursor: workspacePullCursorRef.current,
|
|
1638
|
-
lastPullAt: syncedAt,
|
|
1639
|
-
lastSyncedAt: syncedAt,
|
|
1640
|
-
lastErrorMessage: null
|
|
1641
|
-
});
|
|
1642
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1643
|
-
eventType: workspaceSyncPhase === 'attach-cloud' ? 'bootstrap' : 'pull',
|
|
1644
|
-
status: 'success',
|
|
1645
|
-
changeCount: pullResult.appliedChanges,
|
|
1646
|
-
requestMs: pullResult.pullRequestMs,
|
|
1647
|
-
details: {
|
|
1648
|
-
pages: pullResult.pages,
|
|
1649
|
-
applyMs: pullResult.applyMs,
|
|
1650
|
-
...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
|
|
1651
|
-
}
|
|
1652
|
-
});
|
|
1653
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1654
|
-
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1655
|
-
await refreshWorkspaceSyncAssetsSnapshot();
|
|
1656
|
-
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
1657
|
-
await refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
1658
|
-
seedWorkspaceSnapshotPushBaseline();
|
|
1659
|
-
return true;
|
|
1660
|
-
}
|
|
1661
|
-
const pendingSignature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
|
|
1662
|
-
if (pendingSignature && pendingSignature !== workspaceLastPushedSignatureRef.current) {
|
|
1663
|
-
workspacePendingSignatureRef.current = '';
|
|
1664
|
-
window.setTimeout(() => {
|
|
1665
|
-
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
1666
|
-
}, 120);
|
|
1667
|
-
}
|
|
1668
|
-
return true;
|
|
1669
|
-
}
|
|
1670
|
-
catch (error) {
|
|
1671
|
-
const detail = String(error?.message || '').trim();
|
|
1672
|
-
const message = detail
|
|
1673
|
-
? `Workspace sync pull failed. ${detail}`
|
|
1674
|
-
: 'Workspace sync pull failed.';
|
|
1675
|
-
setUserGlobalSyncStatus('error');
|
|
1676
|
-
setUserGlobalSyncError(message);
|
|
1677
|
-
setWorkspaceLastErrorMessage(message);
|
|
1678
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1679
|
-
scheduleWorkspaceSyncRetry();
|
|
1680
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1681
|
-
eventType: 'pull',
|
|
1682
|
-
status: 'error',
|
|
1683
|
-
errorMessage: message,
|
|
1684
|
-
requestMs: Date.now() - pullStartedAtMs
|
|
1685
|
-
});
|
|
1686
|
-
logSyncDebug('pull_failed_exception', {
|
|
1687
|
-
workspaceId: currentWorkspaceId,
|
|
1688
|
-
elapsedMs: Date.now() - pullStartedAtMs,
|
|
1689
|
-
error: detail || null
|
|
1690
|
-
});
|
|
1691
|
-
return false;
|
|
1692
|
-
}
|
|
1693
|
-
finally {
|
|
1694
|
-
workspacePullInFlightRef.current = false;
|
|
1695
|
-
setWorkspaceSyncBusy(workspacePushInFlightRef.current);
|
|
1696
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1697
|
-
}
|
|
1698
|
-
}, [
|
|
1198
|
+
workspaceLastPushedSignatureRef,
|
|
1199
|
+
workspacePendingSignatureRef,
|
|
1200
|
+
workspaceLastPushedTaskIdsRef,
|
|
1201
|
+
workspaceDeletedTaskIdsRef,
|
|
1202
|
+
workspaceDeletedTaskWatermarksRef,
|
|
1203
|
+
workspacePullCursorRef,
|
|
1204
|
+
workspaceLastPushedWatermarksRef,
|
|
1205
|
+
workspaceLastPushedInitiativeIdsRef,
|
|
1206
|
+
workspaceLastPushedInitiativeWatermarksRef,
|
|
1207
|
+
workspaceLastPushedWorkstreamIdsRef,
|
|
1208
|
+
workspaceLastPushedWorkstreamWatermarksRef,
|
|
1209
|
+
workspaceLastPushedAiProfileIdsRef,
|
|
1210
|
+
workspaceLastPushedAiProfileWatermarksRef,
|
|
1211
|
+
workspaceLastPushedDocumentPathsRef,
|
|
1212
|
+
workspaceLastPushedDocumentWatermarksRef,
|
|
1213
|
+
workspaceLastPushedAssetPathsRef,
|
|
1214
|
+
workspaceLastPushedAssetWatermarksRef,
|
|
1215
|
+
workspaceLastPushedDocumentReviewSessionIdsRef,
|
|
1216
|
+
workspaceLastPushedDocumentReviewSessionWatermarksRef,
|
|
1217
|
+
workspaceLastPushedDocumentReviewCommentIdsRef,
|
|
1218
|
+
workspaceLastPushedDocumentReviewCommentWatermarksRef,
|
|
1219
|
+
workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
|
|
1220
|
+
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
|
|
1221
|
+
workspaceLastPushedTaskEventWatermarksRef,
|
|
1222
|
+
workspaceCaptureAttachBootstrapBaselineRef,
|
|
1223
|
+
workspaceStartupFullReconcileRanRef,
|
|
1224
|
+
workspaceFullReconcileInFlightRef,
|
|
1225
|
+
bootstrapLastProgressAtRef,
|
|
1226
|
+
realtimeSuppressedEventQueueRef,
|
|
1227
|
+
setWorkspaceSyncBusy,
|
|
1228
|
+
setUserGlobalSyncStatus,
|
|
1229
|
+
setUserGlobalSyncError,
|
|
1230
|
+
setWorkspaceLastErrorMessage,
|
|
1231
|
+
setWorkspaceLastErrorAt,
|
|
1232
|
+
setWorkspaceLastPullAt,
|
|
1233
|
+
setWorkspaceLastPushAt,
|
|
1234
|
+
setWorkspaceSyncPendingChanges,
|
|
1235
|
+
setTasks,
|
|
1236
|
+
setArchivedTasks
|
|
1237
|
+
}), [
|
|
1699
1238
|
cloudAuthConfigured,
|
|
1700
1239
|
runtimeMode,
|
|
1701
1240
|
workspaceCloudSyncEnabled,
|
|
1702
1241
|
currentWorkspaceId,
|
|
1703
1242
|
workspaceSyncPhase,
|
|
1704
|
-
|
|
1243
|
+
taxonomyStateFingerprint,
|
|
1705
1244
|
isAuthenticated,
|
|
1706
1245
|
checkAuthSession,
|
|
1707
|
-
clearWorkspaceIssue,
|
|
1708
|
-
ensureWorkspaceSyncReady,
|
|
1709
1246
|
resolveCloudAuthUrl,
|
|
1710
1247
|
ensureCloudWorkspaceReadyForSync,
|
|
1248
|
+
ensureWorkspaceSyncReady,
|
|
1711
1249
|
handleSyncAuthFailure,
|
|
1712
|
-
|
|
1250
|
+
clearWorkspaceRetry,
|
|
1251
|
+
clearWorkspaceIssue,
|
|
1252
|
+
scheduleWorkspaceSyncRetry,
|
|
1253
|
+
persistWorkspaceSyncPatchSafely,
|
|
1254
|
+
persistWorkspaceSyncPatchBestEffort,
|
|
1255
|
+
reportSyncEvent,
|
|
1256
|
+
acquireWorkspaceSyncLease,
|
|
1257
|
+
releaseWorkspaceSyncLease,
|
|
1258
|
+
reportWorkspaceWindowContention,
|
|
1713
1259
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1260
|
+
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1714
1261
|
refreshWorkspaceSyncAssetsSnapshot,
|
|
1715
1262
|
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1716
1263
|
refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
reportSyncEvent,
|
|
1264
|
+
refreshLocalWorkspaceTaskCollections,
|
|
1265
|
+
buildWorkspaceSyncPayload,
|
|
1720
1266
|
buildWorkspaceSyncSignature,
|
|
1721
|
-
pushWorkspaceChangesToCloud,
|
|
1722
1267
|
seedWorkspaceSnapshotPushBaseline,
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
reportWorkspaceWindowContention,
|
|
1727
|
-
workspacePushInFlightRef,
|
|
1728
|
-
workspacePullInFlightRef,
|
|
1729
|
-
persistWorkspaceSyncPatchSafely
|
|
1268
|
+
shouldSuppressAttachBootstrapPush,
|
|
1269
|
+
setTasks,
|
|
1270
|
+
setArchivedTasks
|
|
1730
1271
|
]);
|
|
1731
|
-
|
|
1732
|
-
const
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
if (eventId && realtimeSuppressedEventIdsRef.current.delete(eventId))
|
|
1739
|
-
return;
|
|
1740
|
-
if (workspacePullInFlightRef.current)
|
|
1741
|
-
return;
|
|
1742
|
-
if (isWorkspaceRetryPending()) {
|
|
1743
|
-
clearWorkspaceRetry();
|
|
1744
|
-
}
|
|
1745
|
-
if (realtimePullDebounceRef.current !== null) {
|
|
1746
|
-
window.clearTimeout(realtimePullDebounceRef.current);
|
|
1747
|
-
}
|
|
1748
|
-
realtimePullDebounceRef.current = window.setTimeout(() => {
|
|
1749
|
-
realtimePullDebounceRef.current = null;
|
|
1750
|
-
void pullWorkspaceChangesFromCloud();
|
|
1751
|
-
}, 180);
|
|
1752
|
-
}, [
|
|
1272
|
+
const { pushWorkspaceChangesToCloud, pullWorkspaceChangesFromCloud } = transferCoordinator;
|
|
1273
|
+
const realtimeBridgeRef = useRef({
|
|
1274
|
+
handleRealtimeSignal: () => { },
|
|
1275
|
+
recordSuppressedEventIds: () => { },
|
|
1276
|
+
clearRealtimePullDebounce: () => { }
|
|
1277
|
+
});
|
|
1278
|
+
const realtimeBridge = useMemo(() => createRealtimeSyncBridge({
|
|
1753
1279
|
cloudAuthConfigured,
|
|
1754
1280
|
runtimeMode,
|
|
1755
1281
|
workspaceCloudSyncEnabled,
|
|
1756
1282
|
workspaceSyncPhase,
|
|
1283
|
+
workspacePullInFlightRef,
|
|
1284
|
+
realtimePullDebounceRef,
|
|
1285
|
+
realtimeSuppressedEventIdsRef,
|
|
1286
|
+
clearWorkspaceRetry,
|
|
1757
1287
|
isWorkspaceRetryPending,
|
|
1288
|
+
pullWorkspaceChangesFromCloud
|
|
1289
|
+
}), [
|
|
1290
|
+
cloudAuthConfigured,
|
|
1291
|
+
runtimeMode,
|
|
1292
|
+
workspaceCloudSyncEnabled,
|
|
1293
|
+
workspaceSyncPhase,
|
|
1758
1294
|
clearWorkspaceRetry,
|
|
1759
|
-
|
|
1295
|
+
isWorkspaceRetryPending,
|
|
1760
1296
|
pullWorkspaceChangesFromCloud
|
|
1761
1297
|
]);
|
|
1298
|
+
realtimeBridgeRef.current = realtimeBridge;
|
|
1762
1299
|
const realtimeSocketUrl = resolveWebSocketUrl('/taskforce-ws');
|
|
1763
1300
|
const normalizedRealtimeUserId = String(authUserId || '').trim();
|
|
1764
1301
|
const realtimeEnabled = Boolean(realtimeSyncEnabled
|
|
@@ -1774,7 +1311,7 @@ export function useSyncOrchestrator(input) {
|
|
|
1774
1311
|
enabled: realtimeEnabled,
|
|
1775
1312
|
workspaceId: currentWorkspaceId,
|
|
1776
1313
|
websocketUrl: realtimeSocketUrl,
|
|
1777
|
-
onSignal: handleRealtimeSignal,
|
|
1314
|
+
onSignal: realtimeBridge.handleRealtimeSignal,
|
|
1778
1315
|
onTelemetry: setRealtimeTelemetry,
|
|
1779
1316
|
userId: normalizedRealtimeUserId || undefined
|
|
1780
1317
|
});
|
|
@@ -1782,39 +1319,9 @@ export function useSyncOrchestrator(input) {
|
|
|
1782
1319
|
setRealtimeConnectionState(realtimeSync.connectionState);
|
|
1783
1320
|
}, [realtimeSync.connectionState]);
|
|
1784
1321
|
useEffect(() => {
|
|
1785
|
-
return () =>
|
|
1786
|
-
|
|
1787
|
-
window.clearTimeout(realtimePullDebounceRef.current);
|
|
1788
|
-
realtimePullDebounceRef.current = null;
|
|
1789
|
-
}
|
|
1790
|
-
};
|
|
1791
|
-
}, []);
|
|
1322
|
+
return () => realtimeBridge.clearRealtimePullDebounce();
|
|
1323
|
+
}, [realtimeBridge]);
|
|
1792
1324
|
// === SECTION: Repair & manual retry ===
|
|
1793
|
-
const fetchLocalWorkspaceSnapshot = useCallback(async () => {
|
|
1794
|
-
const [localTasksRes, localArchiveRes] = await Promise.all([
|
|
1795
|
-
fetch('/api/taskforce/tasks', {
|
|
1796
|
-
method: 'GET',
|
|
1797
|
-
credentials: 'include'
|
|
1798
|
-
}),
|
|
1799
|
-
fetch('/api/taskforce/archive', {
|
|
1800
|
-
method: 'GET',
|
|
1801
|
-
credentials: 'include'
|
|
1802
|
-
})
|
|
1803
|
-
]);
|
|
1804
|
-
if (!localTasksRes.ok || !localArchiveRes.ok) {
|
|
1805
|
-
return {
|
|
1806
|
-
tasks: [],
|
|
1807
|
-
archived: [],
|
|
1808
|
-
error: 'Failed to read local workspace snapshot before sync bootstrap.'
|
|
1809
|
-
};
|
|
1810
|
-
}
|
|
1811
|
-
const localTasksPayload = await localTasksRes.json().catch(() => ({}));
|
|
1812
|
-
const localArchivePayload = await localArchiveRes.json().catch(() => ({}));
|
|
1813
|
-
return {
|
|
1814
|
-
tasks: Array.isArray(localTasksPayload?.tasks) ? localTasksPayload.tasks : [],
|
|
1815
|
-
archived: Array.isArray(localArchivePayload?.archived) ? localArchivePayload.archived : []
|
|
1816
|
-
};
|
|
1817
|
-
}, []);
|
|
1818
1325
|
const saveWorkspaceCloudSyncSettings = useCallback(async (settings) => {
|
|
1819
1326
|
return saveWorkspaceCloudSyncSettingsBase(settings, {
|
|
1820
1327
|
cloudAuthConfigured,
|
|
@@ -1830,144 +1337,79 @@ export function useSyncOrchestrator(input) {
|
|
|
1830
1337
|
const retryUserGlobalSettingsSync = useCallback(async () => {
|
|
1831
1338
|
await syncUserGlobalSettings({ preferCloudOnFirstSync: false });
|
|
1832
1339
|
}, [syncUserGlobalSettings]);
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
return false;
|
|
1837
|
-
if (!isAuthenticated) {
|
|
1838
|
-
const authed = await checkAuthSession();
|
|
1839
|
-
if (!authed)
|
|
1840
|
-
return false;
|
|
1841
|
-
}
|
|
1842
|
-
workspaceForceFullAiProfilePushRef.current = true;
|
|
1843
|
-
if (workspaceSyncPhase === 'provision-local') {
|
|
1844
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1845
|
-
if (!signature)
|
|
1846
|
-
return false;
|
|
1847
|
-
return pushWorkspaceChangesToCloud(signature, { forceAllAiProfiles: true });
|
|
1848
|
-
}
|
|
1849
|
-
const pulled = await pullWorkspaceChangesFromCloud();
|
|
1850
|
-
if (workspaceSyncPhase === 'attach-cloud')
|
|
1851
|
-
return pulled;
|
|
1852
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1853
|
-
const forceAllAiProfiles = workspaceForceFullAiProfilePushRef.current === true;
|
|
1854
|
-
if (!forceAllAiProfiles && shouldSuppressAttachBootstrapPush(signature)) {
|
|
1855
|
-
return pulled;
|
|
1856
|
-
}
|
|
1857
|
-
if (signature) {
|
|
1858
|
-
workspacePendingSignatureRef.current = signature;
|
|
1859
|
-
const pushed = await pushWorkspaceChangesToCloud(signature, {
|
|
1860
|
-
forceAllAiProfiles
|
|
1861
|
-
});
|
|
1862
|
-
return pulled || pushed;
|
|
1863
|
-
}
|
|
1864
|
-
return pulled;
|
|
1865
|
-
}, [
|
|
1866
|
-
clearWorkspaceRetry,
|
|
1867
|
-
cloudAuthConfigured,
|
|
1868
|
-
runtimeMode,
|
|
1340
|
+
const recoveryCoordinator = useMemo(() => createSyncRecoveryCoordinator({
|
|
1341
|
+
currentWorkspaceId,
|
|
1342
|
+
taxonomyStateFingerprint,
|
|
1869
1343
|
workspaceCloudSyncEnabled,
|
|
1870
|
-
isAuthenticated,
|
|
1871
|
-
checkAuthSession,
|
|
1872
1344
|
workspaceSyncPhase,
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
workspaceLastPushedDocumentWatermarksRef.current = new Map();
|
|
1906
|
-
workspaceLastPushedAssetPathsRef.current = new Set();
|
|
1907
|
-
workspaceLastPushedAssetWatermarksRef.current = new Map();
|
|
1908
|
-
workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
|
|
1909
|
-
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
|
|
1910
|
-
workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
|
|
1911
|
-
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
|
|
1912
|
-
workspaceForceFullAiProfilePushRef.current = false;
|
|
1913
|
-
workspaceStartupRepairRanRef.current = false;
|
|
1914
|
-
// Clear persisted watermarks so the next push is a full resync (repair intent)
|
|
1915
|
-
savePersistedSyncWatermarks(currentWorkspaceId, {
|
|
1916
|
-
taskWatermarks: new Map(),
|
|
1917
|
-
initiativeWatermarks: new Map(),
|
|
1918
|
-
workstreamWatermarks: new Map(),
|
|
1919
|
-
documentWatermarks: new Map(),
|
|
1920
|
-
assetWatermarks: new Map(),
|
|
1921
|
-
documentReviewSessionWatermarks: new Map(),
|
|
1922
|
-
documentReviewCommentWatermarks: new Map(),
|
|
1923
|
-
annotatedAttachmentSessionWatermarks: new Map(),
|
|
1924
|
-
taxonomyStateFingerprint
|
|
1925
|
-
});
|
|
1926
|
-
try {
|
|
1927
|
-
if (!workspaceCloudSyncEnabled)
|
|
1928
|
-
return;
|
|
1929
|
-
workspaceRepairModeRef.current = true;
|
|
1930
|
-
const repairPhase = workspaceSyncPhase === 'provision-local'
|
|
1931
|
-
? 'provision-local'
|
|
1932
|
-
: workspaceSyncPhase === 'attach-cloud'
|
|
1933
|
-
? 'attach-cloud'
|
|
1934
|
-
: lastBootstrapPhaseRef.current === 'provision-local' || lastBootstrapPhaseRef.current === 'attach-cloud'
|
|
1935
|
-
? lastBootstrapPhaseRef.current
|
|
1936
|
-
: (workspaceLastPullAt ? 'attach-cloud' : 'provision-local');
|
|
1937
|
-
await persistWorkspaceSyncPatch({
|
|
1938
|
-
phase: repairPhase,
|
|
1939
|
-
pullCursor: null,
|
|
1940
|
-
lastErrorMessage: null
|
|
1941
|
-
});
|
|
1942
|
-
if (repairPhase === 'provision-local') {
|
|
1943
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1944
|
-
if (!signature)
|
|
1945
|
-
return;
|
|
1946
|
-
await pushWorkspaceChangesToCloud(signature, { forceAllAiProfiles: true, repairMode: true });
|
|
1947
|
-
return;
|
|
1948
|
-
}
|
|
1949
|
-
await pullWorkspaceChangesFromCloud({ repairMode: true });
|
|
1950
|
-
}
|
|
1951
|
-
finally {
|
|
1952
|
-
workspaceRepairModeRef.current = false;
|
|
1953
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1954
|
-
}
|
|
1955
|
-
}, [
|
|
1345
|
+
workspaceLastPullAt,
|
|
1346
|
+
lastBootstrapPhaseRef,
|
|
1347
|
+
workspaceRepairModeRef,
|
|
1348
|
+
workspaceForceFullAiProfilePushRef,
|
|
1349
|
+
workspaceStartupRepairRanRef,
|
|
1350
|
+
workspaceStartupFullReconcileRanRef,
|
|
1351
|
+
workspaceFullReconcileInFlightRef,
|
|
1352
|
+
workspacePullCursorRef,
|
|
1353
|
+
workspaceAttachBootstrapBaselineSignatureRef,
|
|
1354
|
+
workspaceCaptureAttachBootstrapBaselineRef,
|
|
1355
|
+
workspacePendingSignatureRef,
|
|
1356
|
+
workspaceLastPushedSignatureRef,
|
|
1357
|
+
workspaceLastPushedWatermarksRef,
|
|
1358
|
+
workspaceLastPushedInitiativeIdsRef,
|
|
1359
|
+
workspaceLastPushedInitiativeWatermarksRef,
|
|
1360
|
+
workspaceLastPushedWorkstreamIdsRef,
|
|
1361
|
+
workspaceLastPushedWorkstreamWatermarksRef,
|
|
1362
|
+
workspaceLastPushedAiProfileIdsRef,
|
|
1363
|
+
workspaceLastPushedAiProfileWatermarksRef,
|
|
1364
|
+
workspaceLastPushedDocumentPathsRef,
|
|
1365
|
+
workspaceLastPushedDocumentWatermarksRef,
|
|
1366
|
+
workspaceLastPushedAssetPathsRef,
|
|
1367
|
+
workspaceLastPushedAssetWatermarksRef,
|
|
1368
|
+
workspaceLastPushedDocumentReviewSessionIdsRef,
|
|
1369
|
+
workspaceLastPushedDocumentReviewSessionWatermarksRef,
|
|
1370
|
+
workspaceLastPushedDocumentReviewCommentIdsRef,
|
|
1371
|
+
workspaceLastPushedDocumentReviewCommentWatermarksRef,
|
|
1372
|
+
workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
|
|
1373
|
+
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
|
|
1374
|
+
workspaceLastPushedTaskEventWatermarksRef,
|
|
1375
|
+
bootstrapPhaseStartedAtRef,
|
|
1376
|
+
bootstrapLastProgressAtRef,
|
|
1956
1377
|
acquireWorkspaceSyncLease,
|
|
1378
|
+
releaseWorkspaceSyncLease,
|
|
1379
|
+
forceClearWorkspaceSyncLease,
|
|
1957
1380
|
clearWorkspaceRetry,
|
|
1958
1381
|
clearWorkspaceIssue,
|
|
1382
|
+
reportWorkspaceWindowContention,
|
|
1383
|
+
persistWorkspaceSyncPatch,
|
|
1384
|
+
buildWorkspaceSyncSignature,
|
|
1385
|
+
pullWorkspaceChangesFromCloud,
|
|
1386
|
+
pushWorkspaceChangesToCloud,
|
|
1387
|
+
cloudAuthConfigured,
|
|
1388
|
+
runtimeMode,
|
|
1389
|
+
isAuthenticated,
|
|
1390
|
+
checkAuthSession
|
|
1391
|
+
}), [
|
|
1959
1392
|
currentWorkspaceId,
|
|
1960
|
-
|
|
1393
|
+
taxonomyStateFingerprint,
|
|
1961
1394
|
workspaceCloudSyncEnabled,
|
|
1962
1395
|
workspaceSyncPhase,
|
|
1396
|
+
workspaceLastPullAt,
|
|
1397
|
+
acquireWorkspaceSyncLease,
|
|
1398
|
+
releaseWorkspaceSyncLease,
|
|
1399
|
+
forceClearWorkspaceSyncLease,
|
|
1400
|
+
clearWorkspaceRetry,
|
|
1401
|
+
clearWorkspaceIssue,
|
|
1402
|
+
reportWorkspaceWindowContention,
|
|
1963
1403
|
persistWorkspaceSyncPatch,
|
|
1964
1404
|
buildWorkspaceSyncSignature,
|
|
1965
|
-
workspaceLastPullAt,
|
|
1966
1405
|
pullWorkspaceChangesFromCloud,
|
|
1967
1406
|
pushWorkspaceChangesToCloud,
|
|
1968
|
-
|
|
1969
|
-
|
|
1407
|
+
cloudAuthConfigured,
|
|
1408
|
+
runtimeMode,
|
|
1409
|
+
isAuthenticated,
|
|
1410
|
+
checkAuthSession
|
|
1970
1411
|
]);
|
|
1412
|
+
const { retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull } = recoveryCoordinator;
|
|
1971
1413
|
// === SECTION: Diagnostics & reactive effects ===
|
|
1972
1414
|
const getWorkspaceSyncDiagnostics = useCallback(() => {
|
|
1973
1415
|
return {
|
|
@@ -2026,6 +1468,13 @@ export function useSyncOrchestrator(input) {
|
|
|
2026
1468
|
useEffect(() => {
|
|
2027
1469
|
retryWorkspaceSyncRef.current = retryWorkspaceCloudSync;
|
|
2028
1470
|
}, [retryWorkspaceCloudSync]);
|
|
1471
|
+
const refreshWorkspaceSyncContextAssetSnapshots = useCallback(() => {
|
|
1472
|
+
void refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1473
|
+
void refreshWorkspaceSyncAssetsSnapshot();
|
|
1474
|
+
}, [
|
|
1475
|
+
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1476
|
+
refreshWorkspaceSyncAssetsSnapshot
|
|
1477
|
+
]);
|
|
2029
1478
|
useEffect(() => {
|
|
2030
1479
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
2031
1480
|
return;
|
|
@@ -2041,11 +1490,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2041
1490
|
const eventWorkspaceId = String(detail?.workspaceId || '').trim() || 'default';
|
|
2042
1491
|
if (eventWorkspaceId !== currentWorkspaceId)
|
|
2043
1492
|
return;
|
|
2044
|
-
|
|
2045
|
-
void refreshWorkspaceSyncAiProfilesSnapshot();
|
|
2046
|
-
void refreshWorkspaceSyncAssetsSnapshot();
|
|
2047
|
-
void refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
2048
|
-
void refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
1493
|
+
refreshWorkspaceSyncContextAssetSnapshots();
|
|
2049
1494
|
};
|
|
2050
1495
|
window.addEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
|
|
2051
1496
|
const docsIntervalId = window.setInterval(() => {
|
|
@@ -2078,6 +1523,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2078
1523
|
authSessionResolved,
|
|
2079
1524
|
isAuthenticated,
|
|
2080
1525
|
currentWorkspaceId,
|
|
1526
|
+
refreshWorkspaceSyncContextAssetSnapshots,
|
|
2081
1527
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
2082
1528
|
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
2083
1529
|
refreshWorkspaceSyncAssetsSnapshot,
|
|
@@ -2096,12 +1542,8 @@ export function useSyncOrchestrator(input) {
|
|
|
2096
1542
|
return;
|
|
2097
1543
|
if (!taskAttachmentsFingerprint)
|
|
2098
1544
|
return;
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
void refreshWorkspaceSyncAiProfilesSnapshot();
|
|
2102
|
-
void refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
2103
|
-
void refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
2104
|
-
}, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot]);
|
|
1545
|
+
refreshWorkspaceSyncContextAssetSnapshots();
|
|
1546
|
+
}, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncContextAssetSnapshots]);
|
|
2105
1547
|
useEffect(() => {
|
|
2106
1548
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
2107
1549
|
return;
|
|
@@ -2134,6 +1576,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2134
1576
|
buildWorkspaceSyncSignature,
|
|
2135
1577
|
shouldSuppressAttachBootstrapPush,
|
|
2136
1578
|
buildWorkspaceSyncPayload,
|
|
1579
|
+
workspaceCoreSyncFingerprint,
|
|
2137
1580
|
workspaceSyncAiProfilesFingerprint,
|
|
2138
1581
|
workspaceSyncMarkdownDocumentsFingerprint,
|
|
2139
1582
|
workspaceSyncAssetsFingerprint,
|
|
@@ -2143,6 +1586,41 @@ export function useSyncOrchestrator(input) {
|
|
|
2143
1586
|
workspacePushInFlightRef,
|
|
2144
1587
|
workspacePullInFlightRef
|
|
2145
1588
|
]);
|
|
1589
|
+
useEffect(() => {
|
|
1590
|
+
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
1591
|
+
return;
|
|
1592
|
+
if (workspaceSyncPhase !== 'active')
|
|
1593
|
+
return;
|
|
1594
|
+
if (!workspaceSyncReferenceSnapshotsReady)
|
|
1595
|
+
return;
|
|
1596
|
+
const intervalId = window.setInterval(() => {
|
|
1597
|
+
if (workspacePushInFlightRef.current || workspacePullInFlightRef.current)
|
|
1598
|
+
return;
|
|
1599
|
+
if (isWorkspaceRetryPending())
|
|
1600
|
+
return;
|
|
1601
|
+
const signature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
|
|
1602
|
+
if (!signature || signature === workspaceLastPushedSignatureRef.current)
|
|
1603
|
+
return;
|
|
1604
|
+
const pendingPayload = buildWorkspaceSyncPayload();
|
|
1605
|
+
setWorkspaceSyncPendingChanges(pendingPayload.changes.length);
|
|
1606
|
+
if (pendingPayload.changes.length === 0)
|
|
1607
|
+
return;
|
|
1608
|
+
void pushWorkspaceChangesToCloud(signature);
|
|
1609
|
+
}, workspacePushWatchdogIntervalMs);
|
|
1610
|
+
return () => window.clearInterval(intervalId);
|
|
1611
|
+
}, [
|
|
1612
|
+
cloudAuthConfigured,
|
|
1613
|
+
runtimeMode,
|
|
1614
|
+
workspaceCloudSyncEnabled,
|
|
1615
|
+
authSessionResolved,
|
|
1616
|
+
workspaceSyncPhase,
|
|
1617
|
+
workspaceSyncReferenceSnapshotsReady,
|
|
1618
|
+
buildWorkspaceSyncSignature,
|
|
1619
|
+
buildWorkspaceSyncPayload,
|
|
1620
|
+
pushWorkspaceChangesToCloud,
|
|
1621
|
+
isWorkspaceRetryPending,
|
|
1622
|
+
workspacePushWatchdogIntervalMs
|
|
1623
|
+
]);
|
|
2146
1624
|
useEffect(() => {
|
|
2147
1625
|
if (cloudAuthConfigured && runtimeMode === 'local' && workspaceCloudSyncEnabled)
|
|
2148
1626
|
return;
|
|
@@ -2157,25 +1635,54 @@ export function useSyncOrchestrator(input) {
|
|
|
2157
1635
|
useEffect(() => {
|
|
2158
1636
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
2159
1637
|
return;
|
|
2160
|
-
if (workspaceSyncPhase === 'provision-local')
|
|
1638
|
+
if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'error')
|
|
2161
1639
|
return;
|
|
2162
1640
|
if (isWorkspaceRetryPending())
|
|
2163
1641
|
return;
|
|
2164
|
-
|
|
1642
|
+
const shouldKickOffPull = workspaceSyncPhase === 'attach-cloud' || !workspaceLastPullAt;
|
|
1643
|
+
if (shouldKickOffPull) {
|
|
1644
|
+
void pullWorkspaceChangesFromCloud();
|
|
1645
|
+
}
|
|
2165
1646
|
const intervalId = window.setInterval(() => {
|
|
2166
1647
|
void pullWorkspaceChangesFromCloud();
|
|
2167
1648
|
}, workspacePullIntervalMs);
|
|
2168
|
-
return () =>
|
|
1649
|
+
return () => {
|
|
1650
|
+
window.clearInterval(intervalId);
|
|
1651
|
+
};
|
|
2169
1652
|
}, [
|
|
2170
1653
|
cloudAuthConfigured,
|
|
2171
1654
|
runtimeMode,
|
|
2172
1655
|
workspaceCloudSyncEnabled,
|
|
2173
1656
|
authSessionResolved,
|
|
2174
1657
|
workspaceSyncPhase,
|
|
1658
|
+
workspaceLastPullAt,
|
|
2175
1659
|
isWorkspaceRetryPending,
|
|
2176
1660
|
pullWorkspaceChangesFromCloud,
|
|
2177
1661
|
workspacePullIntervalMs
|
|
2178
1662
|
]);
|
|
1663
|
+
useEffect(() => {
|
|
1664
|
+
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved || !isAuthenticated)
|
|
1665
|
+
return;
|
|
1666
|
+
if (workspaceSyncPhase !== 'active')
|
|
1667
|
+
return;
|
|
1668
|
+
const intervalId = window.setInterval(() => {
|
|
1669
|
+
if (workspaceFullReconcileInFlightRef.current)
|
|
1670
|
+
return;
|
|
1671
|
+
workspaceFullReconcileInFlightRef.current = true;
|
|
1672
|
+
void pullWorkspaceChangesFromCloud({ forceCursorNull: true }).finally(() => {
|
|
1673
|
+
workspaceFullReconcileInFlightRef.current = false;
|
|
1674
|
+
});
|
|
1675
|
+
}, WORKSPACE_FULL_RECONCILE_INTERVAL_MS);
|
|
1676
|
+
return () => window.clearInterval(intervalId);
|
|
1677
|
+
}, [
|
|
1678
|
+
cloudAuthConfigured,
|
|
1679
|
+
runtimeMode,
|
|
1680
|
+
workspaceCloudSyncEnabled,
|
|
1681
|
+
authSessionResolved,
|
|
1682
|
+
isAuthenticated,
|
|
1683
|
+
workspaceSyncPhase,
|
|
1684
|
+
pullWorkspaceChangesFromCloud
|
|
1685
|
+
]);
|
|
2179
1686
|
useEffect(() => {
|
|
2180
1687
|
if (runtimeMode !== 'local' || !isAuthenticated)
|
|
2181
1688
|
return;
|
|
@@ -2277,6 +1784,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2277
1784
|
workspaceLastSuccessfulSyncAt,
|
|
2278
1785
|
workspaceCloudSyncEnabled,
|
|
2279
1786
|
workspaceSyncPhase,
|
|
1787
|
+
workspaceSyncSetupIntent,
|
|
2280
1788
|
workspaceSyncStatus,
|
|
2281
1789
|
workspaceSyncSummary,
|
|
2282
1790
|
workspaceSyncRecommendedAction,
|
|
@@ -2291,6 +1799,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2291
1799
|
workspacePendingSignatureRef,
|
|
2292
1800
|
workspaceLastPushedTaskIdsRef,
|
|
2293
1801
|
workspaceDeletedTaskIdsRef,
|
|
1802
|
+
workspaceDeletedTaskWatermarksRef,
|
|
2294
1803
|
workspacePullCursorRef,
|
|
2295
1804
|
clearWorkspaceRetry,
|
|
2296
1805
|
persistWorkspaceSyncPatch,
|