@taskforcehq/taskforce 0.3.305 → 0.3.307
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 +0 -2
- package/dist/Taskforce.module.css +603 -107
- package/dist/TaskforceCore.js +216 -43
- package/dist/TaskforceCore.test.js +611 -28
- package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/assets/fonts/Noir_medium.woff2 +0 -0
- package/dist/assets/fonts/Noir_regular.woff2 +0 -0
- package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/compat/workspaceSyncCompat.js +46 -2
- package/dist/compat/workspaceSyncCompat.test.js +38 -1
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +561 -0
- package/dist/components/features/AgentsModule.d.ts +14 -1
- package/dist/components/features/AgentsModule.js +366 -18
- package/dist/components/features/AgentsModule.test.js +507 -8
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +7 -1
- package/dist/components/features/DocumentViewer.js +62 -4
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +51 -1
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +24 -29
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +61 -116
- package/dist/components/task/TaskForm.test.js +110 -8
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +94 -21
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +78 -14
- package/dist/components/views/PlanComparisonPage.test.js +257 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +529 -592
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +25 -5
- package/dist/components/views/panels/PlanningDrawer.js +270 -55
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +529 -0
- package/dist/components/views/planningScope.d.ts +20 -0
- package/dist/components/views/planningScope.js +25 -0
- package/dist/components/views/planningScope.test.d.ts +1 -0
- package/dist/components/views/planningScope.test.js +60 -0
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
- package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +46 -2
- package/dist/core/AiProfileService.js +1073 -82
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +1249 -44
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +110 -23
- package/dist/core/AuthIdentityService.d.ts +107 -0
- package/dist/core/AuthIdentityService.js +284 -0
- package/dist/core/AuthTokenService.d.ts +4 -0
- package/dist/core/AuthTokenService.js +34 -6
- package/dist/core/AuthTokenService.test.js +3 -3
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +127 -6
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanEntitlementService.d.ts +23 -2
- package/dist/core/PlanEntitlementService.js +343 -57
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +35 -2
- package/dist/core/PlanningEntities.test.js +418 -4
- package/dist/core/SignupMonetizationSettings.test.js +199 -27
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +42 -42
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +415 -40
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +1543 -266
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
- package/dist/core/WorkspaceLifecycleService.js +0 -5
- package/dist/core/WorkspacePermissions.test.js +25 -1
- package/dist/core/WorkspacePlanMode.test.js +117 -16
- package/dist/core/shared.d.ts +1 -1
- package/dist/core/shared.js +5 -1
- package/dist/core/types.d.ts +75 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +28 -2
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/bootstrap.js +2 -0
- package/dist/hooks/sync/controlPlane.d.ts +37 -0
- package/dist/hooks/sync/controlPlane.js +78 -0
- package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
- package/dist/hooks/sync/controlPlane.test.js +121 -0
- package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
- package/dist/hooks/sync/lifecyclePolicy.js +93 -0
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
- package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +13 -4
- package/dist/hooks/sync/orchestratorShared.js +129 -48
- package/dist/hooks/sync/orchestratorShared.test.js +50 -1
- package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
- package/dist/hooks/sync/pullLifecycle.js +24 -0
- package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
- package/dist/hooks/sync/pullLifecycle.test.js +80 -0
- package/dist/hooks/sync/recovery.d.ts +16 -2
- package/dist/hooks/sync/recovery.js +179 -53
- package/dist/hooks/sync/recovery.test.d.ts +1 -0
- package/dist/hooks/sync/recovery.test.js +292 -0
- package/dist/hooks/sync/transfers.d.ts +16 -3
- package/dist/hooks/sync/transfers.js +210 -66
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +928 -0
- package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
- package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
- package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
- package/dist/hooks/sync/useSyncStatusActions.js +88 -0
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
- package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
- package/dist/hooks/sync/useSyncStatusControls.js +60 -0
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
- package/dist/hooks/useSyncOrchestrator.js +409 -183
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskforce.d.ts +145 -52
- package/dist/hooks/useTaskforce.js +592 -253
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +246 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2468 -201
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +13 -2
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +328 -40
- package/dist/localization/locales/en-US.d.ts +0 -1
- package/dist/localization/locales/en-US.js +0 -1
- package/dist/localization/locales/es-419.js +0 -1
- package/dist/localization/locales/pt-BR.js +0 -1
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +116 -10
- package/dist/mcp/runtime.d.ts +3 -0
- package/dist/mcp/runtime.js +1099 -185
- package/dist/mcp/runtime.test.js +1491 -140
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +192 -61
- package/dist/migrations/taskSchemaMigrations.test.js +15 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/dist/resources/templates/workflows/collaborate.yaml +1 -1
- package/dist/resources/templates/workflows/evaluate.yaml +2 -2
- package/dist/resources/templates/workflows/plan.yaml +1 -1
- package/dist/resources/templates/workflows/review.yaml +2 -2
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +69 -10
- package/dist/server/auth/providers/apple.d.ts +33 -0
- package/dist/server/auth/providers/apple.js +82 -0
- package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
- package/dist/server/auth/providers/appleClientSecret.js +65 -0
- package/dist/server/auth/providers/github.d.ts +26 -0
- package/dist/server/auth/providers/github.js +86 -0
- package/dist/server/auth/providers/google.d.ts +21 -0
- package/dist/server/auth/providers/google.js +56 -0
- package/dist/server/auth/providers/types.d.ts +21 -0
- package/dist/server/auth/providers/types.js +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.js +6 -3
- package/dist/server/documentReviewRoutes.test.js +36 -3
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +124 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +111 -3
- package/dist/server/routes/admin.d.ts +3 -0
- package/dist/server/routes/admin.js +459 -28
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +26 -2
- package/dist/server/routes/auth.js +1170 -71
- package/dist/server/routes/authSupport.d.ts +5 -1
- package/dist/server/routes/authSupport.js +13 -2
- package/dist/server/routes/billing.js +372 -35
- package/dist/server/routes/billing.test.js +595 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +182 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -14
- package/dist/server/routes/sync.integration.test.js +658 -39
- package/dist/server/routes/sync.js +74 -608
- package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
- package/dist/server/routes/syncAuxRoutes.js +91 -0
- package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
- package/dist/server/routes/syncAuxRoutes.test.js +158 -0
- package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
- package/dist/server/routes/syncPullApplyRoutes.js +204 -0
- package/dist/server/routes/syncPushRoutes.d.ts +24 -0
- package/dist/server/routes/syncPushRoutes.js +212 -0
- package/dist/server/routes/syncRouteGuards.d.ts +36 -0
- package/dist/server/routes/syncRouteGuards.js +67 -0
- package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
- package/dist/server/routes/syncRouteGuards.test.js +94 -0
- package/dist/server/routes/syncRouteTypes.d.ts +13 -0
- package/dist/server/routes/syncRouteTypes.js +1 -0
- package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
- package/dist/server/routes/syncSnapshotRoutes.js +182 -0
- package/dist/server/routes/tasks.js +55 -10
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +147 -11
- package/dist/server/routes.test.js +2704 -183
- package/dist/services/workflowExportSnapshots.test.js +2 -0
- package/dist/shared/aiProfileSeatScope.d.ts +5 -0
- package/dist/shared/aiProfileSeatScope.js +21 -0
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/shared/passwordPolicy.d.ts +13 -0
- package/dist/shared/passwordPolicy.js +84 -0
- package/dist/shared/passwordPolicy.test.d.ts +1 -0
- package/dist/shared/passwordPolicy.test.js +34 -0
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/storage/workspaceAssetStore.d.ts +15 -3
- package/dist/storage/workspaceAssetStore.js +132 -44
- package/dist/storage/workspaceAssetStore.test.js +93 -6
- package/dist/styles/fonts.css +70 -0
- package/dist/sync/collaborationSyncPayload.d.ts +6 -1
- package/dist/sync/collaborationSyncPayload.js +19 -11
- package/dist/sync/collaborationSyncPayload.test.js +14 -1
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +9 -9
- package/dist/sync/contentSyncState.js +248 -107
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +11 -4
- package/dist/sync/syncApplyHandlers.js +115 -42
- package/dist/sync/syncApplyHandlers.test.js +251 -4
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +46 -0
- package/dist/sync/syncResourceAdapters.js +79 -0
- package/dist/sync/syncService.d.ts +6 -0
- package/dist/sync/syncService.js +65 -1
- package/dist/sync/syncService.test.js +96 -3
- package/dist/sync/taskSyncChangeKey.d.ts +2 -0
- package/dist/sync/taskSyncChangeKey.js +143 -0
- package/dist/sync/taskSyncPayload.js +3 -8
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +132 -72
- package/dist/sync/workspacePullFeed.test.js +92 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +73 -6
- package/dist/sync/workspaceSyncModel.js +342 -73
- package/dist/sync/workspaceSyncModel.test.js +559 -14
- package/dist/sync/workspaceSyncState.d.ts +17 -0
- package/dist/sync/workspaceSyncState.js +2 -1
- package/dist/sync/workspaceSyncSurface.js +3 -8
- package/dist/types.d.ts +50 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
- package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
- package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/ui/fonts/Noir_medium.otf +0 -0
- package/dist/ui/fonts/Noir_medium.woff2 +0 -0
- package/dist/ui/fonts/Noir_regular.otf +0 -0
- package/dist/ui/fonts/Noir_regular.woff2 +0 -0
- package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +8 -0
- package/dist/utils/assignees.js +10 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/avatarUpload.d.ts +27 -0
- package/dist/utils/avatarUpload.js +132 -0
- package/dist/utils/avatarUpload.test.d.ts +1 -0
- package/dist/utils/avatarUpload.test.js +40 -0
- package/dist/utils/bootstrapTrace.d.ts +7 -0
- package/dist/utils/bootstrapTrace.js +29 -0
- package/dist/utils/commercialLifecyclePresentation.js +3 -0
- package/dist/utils/contextAssetEvents.d.ts +2 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/syncEventDetails.d.ts +3 -0
- package/dist/utils/syncEventDetails.js +64 -0
- package/dist/utils/syncEventDetails.test.d.ts +1 -0
- package/dist/utils/syncEventDetails.test.js +23 -0
- package/dist/utils/syncEventPresentation.d.ts +15 -0
- package/dist/utils/syncEventPresentation.js +129 -0
- package/dist/utils/syncEventPresentation.test.d.ts +1 -0
- package/dist/utils/syncEventPresentation.test.js +64 -0
- package/dist/utils/syncStatusPresentation.d.ts +72 -0
- package/dist/utils/syncStatusPresentation.js +217 -0
- package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
- package/dist/utils/syncStatusPresentation.test.js +164 -0
- package/dist/utils/taskActivity.d.ts +9 -1
- package/dist/utils/taskActivity.js +290 -22
- package/dist/utils/taskActivity.test.js +141 -8
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
- package/dist/utils/workspaceSyncPresentation.js +68 -2
- package/dist/utils/workspaceSyncPresentation.test.js +36 -1
- package/package.json +3 -2
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/run-e2e-realtime.sh +17 -3
- package/scripts/run-smoke.sh +17 -5
- package/scripts/run-soak.sh +17 -5
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/src/resources/templates/workflows/collaborate.yaml +1 -1
- package/src/resources/templates/workflows/evaluate.yaml +2 -2
- package/src/resources/templates/workflows/plan.yaml +1 -1
- package/src/resources/templates/workflows/review.yaml +2 -2
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BG6P_GVW.js +0 -3
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +0 -9
- package/dist/ui/assets/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* === SECTION: Navigation === Line ~3410 — setActiveTab, handleNavigation
|
|
28
28
|
* === SECTION: Form === Line ~3489 — resetForm, handleEdit, handleSubmit, saveTask
|
|
29
29
|
* === SECTION: Task CRUD === Line ~3691 — handleDelete, handleUpdateTask, handleToggle*
|
|
30
|
-
* === SECTION:
|
|
30
|
+
* === SECTION: Planning Links === Line ~4129 — workstream and initiative link handlers
|
|
31
31
|
* === SECTION: Category/Type Mgmt === Line ~4462 — handleSaveCategory, handleRemoveCategory
|
|
32
32
|
* === SECTION: Settings Model === Line ~4625 — settingsModel object
|
|
33
33
|
* === SECTION: Return Value === Line ~4817 — final return object (public API shape)
|
|
@@ -43,6 +43,7 @@ import { DEFAULT_TYPE_VALUE } from '../shared/taxonomyDefaults.js';
|
|
|
43
43
|
import { cloneDefaultWorkflowDefinitions } from '../shared/workflowDefaults.js';
|
|
44
44
|
import { resolveClientDeploymentConfig } from '../config/clientDeployment';
|
|
45
45
|
import { resolveTaskforceMcpBaseUrlFromBaseUrl } from '../config/cloudEnvironment';
|
|
46
|
+
import { buildBootstrapTraceId, withBootstrapTraceHeader } from '../utils/bootstrapTrace';
|
|
46
47
|
import { createDefaultAssigneeOptions } from '../utils/assignees';
|
|
47
48
|
import { persistLocalMutationActorUserId, readLocalMutationActorUserId } from '../utils/taskforceApiClient';
|
|
48
49
|
import { useSyncOrchestrator } from './useSyncOrchestrator';
|
|
@@ -67,6 +68,14 @@ import { useTaskforceAuthBootstrap } from './auth/useTaskforceAuthBootstrap';
|
|
|
67
68
|
import { deriveAuthGuardPolicy } from './auth/useAuthGuardPolicy';
|
|
68
69
|
import { useTaskforceWorkspaceBootstrap } from './workspace/useTaskforceWorkspaceBootstrap';
|
|
69
70
|
import { useDataVersionRefresh } from './sync/useDataVersionRefresh';
|
|
71
|
+
import { BOOTSTRAP_REQUEST_TIMEOUT_MS } from './bootstrapTimeouts';
|
|
72
|
+
function upsertEntityById(items, nextItem) {
|
|
73
|
+
const existingIndex = items.findIndex((item) => item.id === nextItem.id);
|
|
74
|
+
if (existingIndex === -1) {
|
|
75
|
+
return [...items, nextItem];
|
|
76
|
+
}
|
|
77
|
+
return items.map((item, index) => (index === existingIndex ? nextItem : item));
|
|
78
|
+
}
|
|
70
79
|
const ENV_DEPLOYMENT = resolveClientDeploymentConfig(import.meta.env);
|
|
71
80
|
const DEFAULT_CLOUD_APP_ORIGIN = (() => {
|
|
72
81
|
const configuredBase = ENV_DEPLOYMENT.baseUrl;
|
|
@@ -81,7 +90,75 @@ const DEFAULT_CLOUD_APP_ORIGIN = (() => {
|
|
|
81
90
|
// Safety default for local runtime cloud auth/sync fallback.
|
|
82
91
|
return 'https://app.taskforcehq.ai';
|
|
83
92
|
})();
|
|
84
|
-
const
|
|
93
|
+
const PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS = 20_000;
|
|
94
|
+
const EXPECTED_WORKSPACE_ABORT_REASONS = new Set([
|
|
95
|
+
'external-abort',
|
|
96
|
+
'superseded',
|
|
97
|
+
'workspace-reset',
|
|
98
|
+
'workspace-transition',
|
|
99
|
+
]);
|
|
100
|
+
function hasAbortErrorName(error) {
|
|
101
|
+
if (typeof DOMException !== 'undefined' && error instanceof DOMException) {
|
|
102
|
+
return error.name === 'AbortError';
|
|
103
|
+
}
|
|
104
|
+
return String(error?.name || '').toLowerCase() === 'aborterror';
|
|
105
|
+
}
|
|
106
|
+
function isExpectedWorkspaceAbortReason(reason) {
|
|
107
|
+
return typeof reason === 'string' && EXPECTED_WORKSPACE_ABORT_REASONS.has(reason);
|
|
108
|
+
}
|
|
109
|
+
function isExpectedWorkspaceAbortFailure(error, signal) {
|
|
110
|
+
if (!signal.aborted)
|
|
111
|
+
return false;
|
|
112
|
+
if (error === signal.reason)
|
|
113
|
+
return true;
|
|
114
|
+
if (isExpectedWorkspaceAbortReason(error) || isExpectedWorkspaceAbortReason(signal.reason))
|
|
115
|
+
return true;
|
|
116
|
+
return hasAbortErrorName(error);
|
|
117
|
+
}
|
|
118
|
+
function createBootstrapTimeoutError(timeoutMs) {
|
|
119
|
+
const timeoutError = new Error(`Startup checks timed out after ${timeoutMs}ms.`);
|
|
120
|
+
timeoutError.name = 'BootstrapTimeoutError';
|
|
121
|
+
return timeoutError;
|
|
122
|
+
}
|
|
123
|
+
function sanitizePlanningBootstrapPayload(input) {
|
|
124
|
+
const payload = input && typeof input === 'object' ? input : {};
|
|
125
|
+
const initiatives = Array.isArray(payload.initiatives)
|
|
126
|
+
? payload.initiatives.filter((item) => Boolean(item && typeof item === 'object' && typeof item.id === 'string'))
|
|
127
|
+
: [];
|
|
128
|
+
const workstreams = Array.isArray(payload.workstreams)
|
|
129
|
+
? payload.workstreams.filter((item) => Boolean(item && typeof item === 'object' && typeof item.id === 'string'))
|
|
130
|
+
: [];
|
|
131
|
+
const workstreamTaskSummaries = Array.isArray(payload.workstreamTaskSummaries)
|
|
132
|
+
? payload.workstreamTaskSummaries.flatMap((entry) => {
|
|
133
|
+
if (!entry || typeof entry !== 'object')
|
|
134
|
+
return [];
|
|
135
|
+
const normalizedWorkstreamId = String(entry.workstreamId || '').trim();
|
|
136
|
+
if (!normalizedWorkstreamId)
|
|
137
|
+
return [];
|
|
138
|
+
const rawTasks = Array.isArray(entry.tasks) ? entry.tasks : [];
|
|
139
|
+
const taskCount = Math.max(0, Number(entry.taskCount) || 0);
|
|
140
|
+
const completedTaskCount = Math.max(0, Number(entry.completedTaskCount) || 0);
|
|
141
|
+
return [{
|
|
142
|
+
workstreamId: normalizedWorkstreamId,
|
|
143
|
+
taskCount,
|
|
144
|
+
completedTaskCount,
|
|
145
|
+
tasks: rawTasks
|
|
146
|
+
.filter((task) => Boolean(task && typeof task === 'object' && typeof task.id === 'string'))
|
|
147
|
+
.map((task) => ({
|
|
148
|
+
id: String(task.id || '').trim(),
|
|
149
|
+
referenceNumber: typeof task.referenceNumber === 'number' ? task.referenceNumber : null,
|
|
150
|
+
title: String(task.title || '').trim() || 'Untitled Task',
|
|
151
|
+
status: typeof task.status === 'string' ? task.status : null,
|
|
152
|
+
})),
|
|
153
|
+
}];
|
|
154
|
+
})
|
|
155
|
+
: [];
|
|
156
|
+
return {
|
|
157
|
+
initiatives,
|
|
158
|
+
workstreams,
|
|
159
|
+
workstreamTaskSummaries,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
85
162
|
export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, onClose }) {
|
|
86
163
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
87
164
|
const { categories, types, apiEndpoint, apiBaseUrl, cloudAuthBaseUrl, cloudMcpBaseUrl, wsBaseUrl, shortcut: configShortcut } = mergedConfig;
|
|
@@ -131,6 +208,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
131
208
|
const workspaceMode = workspaceModeRaw === 'single-local' || workspaceModeRaw === 'multi-cloud'
|
|
132
209
|
? workspaceModeRaw
|
|
133
210
|
: (runtimeModeHint === 'cloud' ? 'multi-cloud' : 'single-local');
|
|
211
|
+
setRuntimeMode(runtimeModeHint);
|
|
134
212
|
setRuntimeConfigOverride({
|
|
135
213
|
cloudEnvironment: typeof cfg.cloudEnvironment === 'string'
|
|
136
214
|
? String(cfg.cloudEnvironment).trim().toLowerCase()
|
|
@@ -175,10 +253,11 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
175
253
|
const propApiBaseUrl = normalizeBaseUrl(apiBaseUrl);
|
|
176
254
|
const propCloudAuthBaseUrl = normalizeBaseUrl(cloudAuthBaseUrl);
|
|
177
255
|
const propCloudMcpBaseUrl = normalizeBaseUrl(cloudMcpBaseUrl);
|
|
178
|
-
const explicitConfiguredApiBaseUrl = propApiBaseUrl || envApiBaseUrl || envBaseUrl;
|
|
179
256
|
const runtimeResolvedApiBaseUrl = runtimeConfigOverride.apiBaseUrl || runtimeConfigOverride.baseUrl;
|
|
180
|
-
const
|
|
257
|
+
const envConfiguredApiBaseUrl = isLoopbackHost ? '' : (envApiBaseUrl || envBaseUrl);
|
|
258
|
+
const normalizedApiBaseUrl = propApiBaseUrl
|
|
181
259
|
|| runtimeResolvedApiBaseUrl
|
|
260
|
+
|| envConfiguredApiBaseUrl
|
|
182
261
|
|| '';
|
|
183
262
|
const explicitConfiguredCloudAuthBaseUrl = propCloudAuthBaseUrl || envCloudAuthBaseUrl || envBaseUrl;
|
|
184
263
|
const runtimeResolvedCloudAuthBaseUrl = runtimeConfigOverride.cloudAuthBaseUrl || runtimeConfigOverride.baseUrl;
|
|
@@ -206,10 +285,14 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
206
285
|
|| normalizedCloudAuthBaseUrl
|
|
207
286
|
|| defaultCloudAuthBaseUrl).trim().replace(/\/+$/, '');
|
|
208
287
|
const authOnlyCloudMode = !isCloudHost && Boolean(normalizedCloudAuthBaseUrl) && !normalizedApiBaseUrl;
|
|
209
|
-
|
|
288
|
+
// Real cloud hosts must defer protected bootstrap calls immediately, even before
|
|
289
|
+
// runtime-config finishes, or the one-shot initial bootstrap can consume itself
|
|
290
|
+
// on unauthenticated /config, /taxonomy-state, and /planning/bootstrap reads.
|
|
291
|
+
const shouldProbeCloudAuth = isCloudHost || (runtimeConfigReady && Boolean(normalizedCloudAuthBaseUrl || normalizedApiBaseUrl));
|
|
210
292
|
const shouldGateProtectedApiCalls = shouldProbeCloudAuth && !authOnlyCloudMode;
|
|
211
293
|
const cloudAuthConfigured = Boolean(normalizedCloudAuthBaseUrl || normalizedApiBaseUrl || isCloudHost);
|
|
212
294
|
const initialRuntimeMode = isCloudHost ? 'cloud' : 'local';
|
|
295
|
+
const [runtimeMode, setRuntimeMode] = useState(() => initialRuntimeMode);
|
|
213
296
|
const optimisticLocalAuthUserId = initialRuntimeMode === 'local' && isLoopbackHost && cloudAuthConfigured
|
|
214
297
|
? readLocalMutationActorUserId()
|
|
215
298
|
: '';
|
|
@@ -236,6 +319,9 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
236
319
|
|| path.startsWith('/api/taskforce/sync/')
|
|
237
320
|
|| path.startsWith('/api/taskforce/settings/mcp/');
|
|
238
321
|
if (!isCloudHost && isCloudScopedPath) {
|
|
322
|
+
if (runtimeMode === 'cloud') {
|
|
323
|
+
return path;
|
|
324
|
+
}
|
|
239
325
|
if (!runtimeConfigReady || runtimeConfigOverride.cloudAuthViaLocalProxy) {
|
|
240
326
|
return path;
|
|
241
327
|
}
|
|
@@ -263,7 +349,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
263
349
|
if (!isCloudHost && isCloudScopedPath)
|
|
264
350
|
return path;
|
|
265
351
|
return path;
|
|
266
|
-
}, [runtimeConfigReady, runtimeConfigOverride.cloudAuthViaLocalProxy, runtimeConfigOverride.workspaceMode, normalizedCloudAuthBaseUrl, normalizedApiBaseUrl, isCloudHost]);
|
|
352
|
+
}, [runtimeConfigReady, runtimeConfigOverride.cloudAuthViaLocalProxy, runtimeConfigOverride.workspaceMode, normalizedCloudAuthBaseUrl, normalizedApiBaseUrl, isCloudHost, runtimeMode]);
|
|
267
353
|
const resolveWebSocketUrl = useCallback((path) => {
|
|
268
354
|
const wsPath = String(path || '').trim() || '/taskforce-ws';
|
|
269
355
|
const normalizedPath = wsPath.startsWith('/') ? wsPath : `/${wsPath}`;
|
|
@@ -313,7 +399,6 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
313
399
|
}), [projectRoot, initialRuntimeMode]);
|
|
314
400
|
const [mcpScriptPath, setMcpScriptPath] = useState('');
|
|
315
401
|
const [tenantId, setTenantId] = useState('');
|
|
316
|
-
const [runtimeMode, setRuntimeMode] = useState(() => initialRuntimeMode);
|
|
317
402
|
const runtimeContractFallback = resolveRuntimeModeContract(runtimeMode);
|
|
318
403
|
const workspaceMode = runtimeConfigOverride.workspaceMode === 'single-local' || runtimeConfigOverride.workspaceMode === 'multi-cloud'
|
|
319
404
|
? runtimeConfigOverride.workspaceMode
|
|
@@ -328,6 +413,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
328
413
|
const [authWorkspaceId, setAuthWorkspaceId] = useState('');
|
|
329
414
|
const [authUserEmail, setAuthUserEmail] = useState('');
|
|
330
415
|
const [authUserDisplayName, setAuthUserDisplayName] = useState('');
|
|
416
|
+
const [availableAuthProviders, setAvailableAuthProviders] = useState([]);
|
|
331
417
|
const [authUserAvatarUrl, setAuthUserAvatarUrl] = useState('');
|
|
332
418
|
const [hasBetaAccess, setHasBetaAccess] = useState(true);
|
|
333
419
|
const [hydratedWorkspaceRole, setHydratedWorkspaceRole] = useState(null);
|
|
@@ -358,6 +444,12 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
358
444
|
const currentWorkspaceIdRef = useRef(currentWorkspaceId);
|
|
359
445
|
const explicitWorkspaceSelectionRef = useRef(null);
|
|
360
446
|
const workspaceTransitionEpochRef = useRef(0);
|
|
447
|
+
const bootstrapTraceSeedRef = useRef((() => {
|
|
448
|
+
const randomPart = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
449
|
+
? crypto.randomUUID()
|
|
450
|
+
: `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
451
|
+
return randomPart.replace(/[^A-Za-z0-9_-]+/g, '').slice(0, 12) || 'session';
|
|
452
|
+
})());
|
|
361
453
|
const [workspaceTransitionEpoch, setWorkspaceTransitionEpoch] = useState(0);
|
|
362
454
|
const bootstrapConfigAbortRef = useRef(null);
|
|
363
455
|
const referenceDataAbortRef = useRef(null);
|
|
@@ -365,6 +457,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
365
457
|
const assigneeOptionsAbortRef = useRef(null);
|
|
366
458
|
const deletedTasksAbortRef = useRef(null);
|
|
367
459
|
const planningEntitiesAbortRef = useRef(null);
|
|
460
|
+
const planningEntitiesRetryTimerRef = useRef(null);
|
|
368
461
|
const commitCurrentWorkspaceId = useCallback((nextWorkspaceIdRaw, options) => {
|
|
369
462
|
const nextWorkspaceId = String(nextWorkspaceIdRaw || '').trim();
|
|
370
463
|
if (!nextWorkspaceId)
|
|
@@ -409,9 +502,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
409
502
|
return workspaceTransitionEpochRef.current !== requestState.epoch;
|
|
410
503
|
}, []);
|
|
411
504
|
const isAbortError = useCallback((error) => {
|
|
412
|
-
return error
|
|
413
|
-
? error.name === 'AbortError'
|
|
414
|
-
: String(error?.name || '').toLowerCase() === 'aborterror';
|
|
505
|
+
return hasAbortErrorName(error) || isExpectedWorkspaceAbortReason(error);
|
|
415
506
|
}, []);
|
|
416
507
|
const abortWorkspaceScopedRequests = useCallback(() => {
|
|
417
508
|
[
|
|
@@ -425,6 +516,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
425
516
|
ref.current?.abort('workspace-transition');
|
|
426
517
|
ref.current = null;
|
|
427
518
|
});
|
|
519
|
+
if (planningEntitiesRetryTimerRef.current !== null && typeof window !== 'undefined') {
|
|
520
|
+
window.clearTimeout(planningEntitiesRetryTimerRef.current);
|
|
521
|
+
planningEntitiesRetryTimerRef.current = null;
|
|
522
|
+
}
|
|
428
523
|
referenceDataRequestRef.current = null;
|
|
429
524
|
}, []);
|
|
430
525
|
// Local runtime APIs must stay available immediately on app start, even when cloud
|
|
@@ -504,6 +599,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
504
599
|
const [archivedTasks, setArchivedTasks] = useState([]);
|
|
505
600
|
const [initiatives, setInitiatives] = useState([]);
|
|
506
601
|
const [workstreams, setWorkstreams] = useState([]);
|
|
602
|
+
const [planningBootstrapTaskSummaries, setPlanningBootstrapTaskSummaries] = useState([]);
|
|
507
603
|
const [deletedTasks, setDeletedTasks] = useState([]);
|
|
508
604
|
const [loadingTasks, setLoadingTasks] = useState(false);
|
|
509
605
|
const [editingTaskId, setEditingTaskId] = useState(null);
|
|
@@ -545,6 +641,14 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
545
641
|
const abortController = new AbortController();
|
|
546
642
|
const externalSignal = options?.signal;
|
|
547
643
|
const timeoutLabel = Number(timeoutMs) > 0 ? Number(timeoutMs) : BOOTSTRAP_REQUEST_TIMEOUT_MS;
|
|
644
|
+
const requestState = getWorkspaceRequestState();
|
|
645
|
+
const tracedInit = url.startsWith('/api/taskforce/')
|
|
646
|
+
? withBootstrapTraceHeader(init, buildBootstrapTraceId({
|
|
647
|
+
workspaceId: requestState.workspaceId,
|
|
648
|
+
epoch: requestState.epoch,
|
|
649
|
+
seed: bootstrapTraceSeedRef.current,
|
|
650
|
+
}))
|
|
651
|
+
: init;
|
|
548
652
|
const timerId = typeof window !== 'undefined'
|
|
549
653
|
? window.setTimeout(() => abortController.abort('bootstrap-timeout'), timeoutLabel)
|
|
550
654
|
: null;
|
|
@@ -561,18 +665,16 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
561
665
|
}
|
|
562
666
|
try {
|
|
563
667
|
return await fetch(url, {
|
|
564
|
-
...(
|
|
668
|
+
...(tracedInit || {}),
|
|
565
669
|
signal: abortController.signal
|
|
566
670
|
});
|
|
567
671
|
}
|
|
568
672
|
catch (error) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (
|
|
573
|
-
|
|
574
|
-
timeoutError.name = 'BootstrapTimeoutError';
|
|
575
|
-
throw timeoutError;
|
|
673
|
+
if (externalSignal?.aborted && isExpectedWorkspaceAbortFailure(error, externalSignal)) {
|
|
674
|
+
throw externalSignal.reason || error;
|
|
675
|
+
}
|
|
676
|
+
if (hasAbortErrorName(error) || error === 'bootstrap-timeout') {
|
|
677
|
+
throw createBootstrapTimeoutError(timeoutLabel);
|
|
576
678
|
}
|
|
577
679
|
throw error;
|
|
578
680
|
}
|
|
@@ -584,7 +686,50 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
584
686
|
window.clearTimeout(timerId);
|
|
585
687
|
}
|
|
586
688
|
}
|
|
587
|
-
}, []);
|
|
689
|
+
}, [getWorkspaceRequestState]);
|
|
690
|
+
const bootstrapState = useMemo(() => {
|
|
691
|
+
const authPending = !authSessionResolved;
|
|
692
|
+
const stalled = bootstrapPhase === 'stalled';
|
|
693
|
+
const appPending = authSessionResolved && (!configLoaded || workspaceBootstrapPending || stalled);
|
|
694
|
+
let subtitle = 'Checking account, workspace access, and setup status...';
|
|
695
|
+
if (bootstrapPhase === 'auth') {
|
|
696
|
+
subtitle = 'Verifying session...';
|
|
697
|
+
}
|
|
698
|
+
else if (bootstrapPhase === 'workspace') {
|
|
699
|
+
subtitle = 'Resolving workspace access...';
|
|
700
|
+
}
|
|
701
|
+
else if (bootstrapPhase === 'config') {
|
|
702
|
+
subtitle = 'Loading configuration...';
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
phase: bootstrapPhase,
|
|
706
|
+
auth: {
|
|
707
|
+
resolved: authSessionResolved,
|
|
708
|
+
pending: authPending
|
|
709
|
+
},
|
|
710
|
+
config: {
|
|
711
|
+
loaded: configLoaded
|
|
712
|
+
},
|
|
713
|
+
workspace: {
|
|
714
|
+
pending: workspaceBootstrapPending
|
|
715
|
+
},
|
|
716
|
+
pending: authPending || appPending,
|
|
717
|
+
authPending,
|
|
718
|
+
appPending,
|
|
719
|
+
ready: authSessionResolved && configLoaded && !workspaceBootstrapPending && bootstrapPhase === 'ready',
|
|
720
|
+
stalled,
|
|
721
|
+
subtitle,
|
|
722
|
+
error: bootstrapError,
|
|
723
|
+
startedAt: bootstrapStartedAt
|
|
724
|
+
};
|
|
725
|
+
}, [
|
|
726
|
+
authSessionResolved,
|
|
727
|
+
bootstrapError,
|
|
728
|
+
bootstrapPhase,
|
|
729
|
+
bootstrapStartedAt,
|
|
730
|
+
configLoaded,
|
|
731
|
+
workspaceBootstrapPending
|
|
732
|
+
]);
|
|
588
733
|
const isBootstrapTimeoutError = useCallback((error) => {
|
|
589
734
|
return String(error?.name || '') === 'BootstrapTimeoutError';
|
|
590
735
|
}, []);
|
|
@@ -653,7 +798,6 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
653
798
|
useEffect(() => {
|
|
654
799
|
if (workspaceTransitionEpoch === 0)
|
|
655
800
|
return;
|
|
656
|
-
abortWorkspaceScopedRequests();
|
|
657
801
|
referenceDataRequestRef.current = null;
|
|
658
802
|
lastLoadedUiStateKeyRef.current = '';
|
|
659
803
|
lastAssigneeOptionsLoadKeyRef.current = '';
|
|
@@ -668,8 +812,6 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
668
812
|
setDeletedTasks([]);
|
|
669
813
|
setTasks([]);
|
|
670
814
|
setArchivedTasks([]);
|
|
671
|
-
setInitiatives([]);
|
|
672
|
-
setWorkstreams([]);
|
|
673
815
|
setLoadingTasks(false);
|
|
674
816
|
if (cloudRealtimeFollowUpRefreshTimerRef.current !== null && typeof window !== 'undefined') {
|
|
675
817
|
window.clearTimeout(cloudRealtimeFollowUpRefreshTimerRef.current);
|
|
@@ -679,7 +821,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
679
821
|
window.clearTimeout(archiveBootstrapTimerRef.current);
|
|
680
822
|
archiveBootstrapTimerRef.current = null;
|
|
681
823
|
}
|
|
682
|
-
}, [
|
|
824
|
+
}, [setArchivedTasks, setLoadingTasks, setTasks, workspaceTransitionEpoch]);
|
|
683
825
|
const fetchDeletedTasks = useCallback(async (isSilent = false, options) => {
|
|
684
826
|
const ignoreAuthGuard = options?.ignoreAuthGuard === true;
|
|
685
827
|
if (!ignoreAuthGuard && (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls))
|
|
@@ -727,7 +869,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
727
869
|
}
|
|
728
870
|
}
|
|
729
871
|
catch (err) {
|
|
730
|
-
if (isAbortError(err))
|
|
872
|
+
if (isExpectedWorkspaceAbortFailure(err, abortController.signal) || isAbortError(err))
|
|
731
873
|
return;
|
|
732
874
|
console.error('[Taskforce] Failed to fetch deleted tasks:', err);
|
|
733
875
|
}
|
|
@@ -751,37 +893,119 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
751
893
|
if (!ignoreAuthGuard && (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls))
|
|
752
894
|
return;
|
|
753
895
|
const requestState = getWorkspaceRequestState();
|
|
896
|
+
planningEntitiesAbortRef.current?.abort('superseded');
|
|
897
|
+
if (planningEntitiesRetryTimerRef.current !== null && typeof window !== 'undefined') {
|
|
898
|
+
window.clearTimeout(planningEntitiesRetryTimerRef.current);
|
|
899
|
+
planningEntitiesRetryTimerRef.current = null;
|
|
900
|
+
}
|
|
754
901
|
const abortController = new AbortController();
|
|
755
902
|
planningEntitiesAbortRef.current = abortController;
|
|
756
903
|
try {
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
904
|
+
const planningTraceId = buildBootstrapTraceId({
|
|
905
|
+
workspaceId: requestState.workspaceId,
|
|
906
|
+
epoch: requestState.epoch,
|
|
907
|
+
seed: bootstrapTraceSeedRef.current,
|
|
908
|
+
});
|
|
909
|
+
const fetchPlanningRoute = async (url) => {
|
|
910
|
+
if (typeof window === 'undefined') {
|
|
911
|
+
return fetch(url, withBootstrapTraceHeader({ signal: abortController.signal }, planningTraceId));
|
|
912
|
+
}
|
|
913
|
+
const timeoutController = new AbortController();
|
|
914
|
+
const timerId = window.setTimeout(() => timeoutController.abort('planning-timeout'), PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS);
|
|
915
|
+
const onAbort = () => timeoutController.abort(abortController.signal.reason || 'external-abort');
|
|
916
|
+
if (abortController.signal.aborted) {
|
|
917
|
+
onAbort();
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
abortController.signal.addEventListener('abort', onAbort, { once: true });
|
|
921
|
+
}
|
|
922
|
+
try {
|
|
923
|
+
return await fetch(url, withBootstrapTraceHeader({ signal: timeoutController.signal }, planningTraceId));
|
|
924
|
+
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
if (abortController.signal.aborted)
|
|
927
|
+
throw abortController.signal.reason || error;
|
|
928
|
+
if (hasAbortErrorName(error) || error === 'planning-timeout') {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
finally {
|
|
934
|
+
window.clearTimeout(timerId);
|
|
935
|
+
abortController.signal.removeEventListener('abort', onAbort);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
let bootstrapPayload = null;
|
|
939
|
+
const bootstrapRes = await fetchPlanningRoute('/api/taskforce/planning/bootstrap');
|
|
940
|
+
if (bootstrapRes?.status === 401) {
|
|
762
941
|
handleUnauthorized();
|
|
763
942
|
setInitiatives([]);
|
|
764
943
|
setWorkstreams([]);
|
|
944
|
+
setPlanningBootstrapTaskSummaries([]);
|
|
765
945
|
return;
|
|
766
946
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
947
|
+
if (bootstrapRes === null) {
|
|
948
|
+
throw createBootstrapTimeoutError(PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS);
|
|
949
|
+
}
|
|
950
|
+
if (bootstrapRes.ok) {
|
|
951
|
+
bootstrapPayload = sanitizePlanningBootstrapPayload(await bootstrapRes.json().catch(() => ({})));
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
const [initiativesRes, workstreamsRes] = await Promise.all([
|
|
955
|
+
fetchPlanningRoute('/api/taskforce/initiatives'),
|
|
956
|
+
fetchPlanningRoute('/api/taskforce/workstreams'),
|
|
957
|
+
]);
|
|
958
|
+
if (!initiativesRes || !workstreamsRes) {
|
|
959
|
+
throw new Error('Planning endpoints timed out during startup.');
|
|
960
|
+
}
|
|
961
|
+
if (initiativesRes.status === 401 || workstreamsRes.status === 401) {
|
|
962
|
+
handleUnauthorized();
|
|
963
|
+
setInitiatives([]);
|
|
964
|
+
setWorkstreams([]);
|
|
965
|
+
setPlanningBootstrapTaskSummaries([]);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const [initiativesData, workstreamsData] = await Promise.all([
|
|
969
|
+
initiativesRes.ok ? initiativesRes.json().catch(() => []) : [],
|
|
970
|
+
workstreamsRes.ok ? workstreamsRes.json().catch(() => []) : [],
|
|
971
|
+
]);
|
|
972
|
+
bootstrapPayload = sanitizePlanningBootstrapPayload({
|
|
973
|
+
initiatives: initiativesData,
|
|
974
|
+
workstreams: workstreamsData,
|
|
975
|
+
workstreamTaskSummaries: [],
|
|
976
|
+
});
|
|
977
|
+
}
|
|
771
978
|
if (isWorkspaceRequestStale(requestState)) {
|
|
772
979
|
return;
|
|
773
980
|
}
|
|
774
|
-
setInitiatives(
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
setWorkstreams(Array.isArray(workstreamsData)
|
|
778
|
-
? workstreamsData.filter((item) => Boolean(item && typeof item === 'object' && typeof item.id === 'string'))
|
|
779
|
-
: []);
|
|
981
|
+
setInitiatives(bootstrapPayload.initiatives);
|
|
982
|
+
setWorkstreams(bootstrapPayload.workstreams);
|
|
983
|
+
setPlanningBootstrapTaskSummaries(bootstrapPayload.workstreamTaskSummaries);
|
|
780
984
|
}
|
|
781
985
|
catch (err) {
|
|
782
|
-
if (isAbortError(err))
|
|
986
|
+
if (isExpectedWorkspaceAbortFailure(err, abortController.signal) || isAbortError(err))
|
|
783
987
|
return;
|
|
784
|
-
|
|
988
|
+
const startupTimeout = isBootstrapTimeoutError(err);
|
|
989
|
+
if (startupTimeout) {
|
|
990
|
+
logBootstrapDebug('planning_bootstrap_timeout', {
|
|
991
|
+
workspaceId: requestState.workspaceId,
|
|
992
|
+
epoch: requestState.epoch,
|
|
993
|
+
timeoutMs: PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS,
|
|
994
|
+
});
|
|
995
|
+
console.warn('[Taskforce] Planning bootstrap timed out during startup; retrying in background.');
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
console.error('[Taskforce] Failed to fetch planning entities:', err);
|
|
999
|
+
}
|
|
1000
|
+
if (typeof window !== 'undefined'
|
|
1001
|
+
&& !isWorkspaceRequestStale(requestState)
|
|
1002
|
+
&& initiatives.length === 0
|
|
1003
|
+
&& workstreams.length === 0) {
|
|
1004
|
+
planningEntitiesRetryTimerRef.current = window.setTimeout(() => {
|
|
1005
|
+
planningEntitiesRetryTimerRef.current = null;
|
|
1006
|
+
void fetchPlanningEntities(options);
|
|
1007
|
+
}, 3000);
|
|
1008
|
+
}
|
|
785
1009
|
}
|
|
786
1010
|
finally {
|
|
787
1011
|
if (planningEntitiesAbortRef.current === abortController) {
|
|
@@ -789,12 +1013,16 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
789
1013
|
}
|
|
790
1014
|
}
|
|
791
1015
|
}, [
|
|
1016
|
+
BOOTSTRAP_REQUEST_TIMEOUT_MS,
|
|
1017
|
+
PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS,
|
|
1018
|
+
initiatives.length,
|
|
792
1019
|
getWorkspaceRequestState,
|
|
793
1020
|
handleUnauthorized,
|
|
794
1021
|
isAbortError,
|
|
795
1022
|
isWorkspaceRequestStale,
|
|
796
1023
|
shouldDeferProtectedApiCalls,
|
|
797
1024
|
shouldBlockProtectedApiCalls,
|
|
1025
|
+
workstreams.length,
|
|
798
1026
|
]);
|
|
799
1027
|
const fetchTasks = useCallback(async (isSilent = false, options) => {
|
|
800
1028
|
await fetchTasksBase(isSilent, options);
|
|
@@ -946,7 +1174,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
946
1174
|
}
|
|
947
1175
|
});
|
|
948
1176
|
checkAuthSessionImplRef.current = checkAuthSessionImpl;
|
|
949
|
-
const [activeWorkspaceModule,
|
|
1177
|
+
const [activeWorkspaceModule, setActiveWorkspaceModuleState] = useState('tasks');
|
|
950
1178
|
const [showComments, setShowComments] = useState(false);
|
|
951
1179
|
const [descriptionFocused, setDescriptionFocused] = useState(false);
|
|
952
1180
|
const [isCapturingScreenshot, setIsCapturingScreenshot] = useState(false);
|
|
@@ -967,10 +1195,17 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
967
1195
|
const lastTaskSurfaceLoadKeyRef = useRef('');
|
|
968
1196
|
const lastSettingsSurfaceLoadKeyRef = useRef('');
|
|
969
1197
|
const skipNextUiStatePersistRef = useRef(false);
|
|
1198
|
+
const activeWorkspaceModuleOverridePendingRef = useRef(false);
|
|
970
1199
|
const referenceDataRequestRef = useRef(null);
|
|
971
1200
|
const workspaceListHydratedRef = useRef(false);
|
|
972
1201
|
const hasBootstrappedDataRef = useRef(false);
|
|
973
1202
|
const [uiStateReady, setUiStateReady] = useState(false);
|
|
1203
|
+
const setActiveWorkspaceModule = useCallback((nextModule) => {
|
|
1204
|
+
if (!uiStateReady) {
|
|
1205
|
+
activeWorkspaceModuleOverridePendingRef.current = true;
|
|
1206
|
+
}
|
|
1207
|
+
setActiveWorkspaceModuleState(nextModule);
|
|
1208
|
+
}, [uiStateReady]);
|
|
974
1209
|
const { handleSaveSettings, handleSaveTheme, handleSaveGlobalTheme, handleJsonBackupEnabledChange, handleSaveGlobalJsonBackupEnabled, handleSaveGlobalWeekStartsOn, handleSaveLocale, handleManualComplexityEnabledChange, handleChecklistDropdownEnabledChange, handleShowTaskCardStatusLabelChange, handleResetProjectToGlobal } = useSettingsPersistence({
|
|
975
1210
|
keyShortcut,
|
|
976
1211
|
themeUseGlobalDefault,
|
|
@@ -1003,7 +1238,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1003
1238
|
authRequiredForApiRef.current = authRequiredForApi;
|
|
1004
1239
|
isAuthenticatedRef.current = isAuthenticated;
|
|
1005
1240
|
}, [authSessionResolved, authRequiredForApi, isAuthenticated]);
|
|
1006
|
-
const applyPersistedUiState = useCallback((state) => {
|
|
1241
|
+
const applyPersistedUiState = useCallback((state, options) => {
|
|
1007
1242
|
if (!state)
|
|
1008
1243
|
return;
|
|
1009
1244
|
const hasPersistedFilterState = Array.isArray(state.filterCategories)
|
|
@@ -1013,11 +1248,13 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1013
1248
|
|| Array.isArray(state.filterAssignees)
|
|
1014
1249
|
|| typeof state.filterAssigneesAllSelected === 'boolean'
|
|
1015
1250
|
|| (state.filterTaxonomies && typeof state.filterTaxonomies === 'object');
|
|
1016
|
-
if (
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1251
|
+
if (!options?.skipActiveWorkspaceModule) {
|
|
1252
|
+
if (typeof state.activeWorkspaceModule === 'string') {
|
|
1253
|
+
setActiveWorkspaceModuleState(state.activeWorkspaceModule);
|
|
1254
|
+
}
|
|
1255
|
+
else if (state.groupBy === 'docs') {
|
|
1256
|
+
setActiveWorkspaceModuleState('docs');
|
|
1257
|
+
}
|
|
1021
1258
|
}
|
|
1022
1259
|
if (state.groupBy && state.groupBy !== 'docs')
|
|
1023
1260
|
setGroupBy(state.groupBy);
|
|
@@ -1081,19 +1318,24 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1081
1318
|
if (currentWorkspaceIdRef.current !== targetWorkspaceId) {
|
|
1082
1319
|
return;
|
|
1083
1320
|
}
|
|
1084
|
-
applyPersistedUiState(state
|
|
1321
|
+
applyPersistedUiState(state, {
|
|
1322
|
+
skipActiveWorkspaceModule: activeWorkspaceModuleOverridePendingRef.current
|
|
1323
|
+
});
|
|
1085
1324
|
lastLoadedUiStateKeyRef.current = uiStateLoadKey;
|
|
1086
1325
|
}
|
|
1087
1326
|
catch {
|
|
1088
1327
|
const localState = readPersistedAppUiState(workspaceUiStateScope);
|
|
1089
1328
|
if (currentWorkspaceIdRef.current === targetWorkspaceId) {
|
|
1090
|
-
applyPersistedUiState(localState
|
|
1329
|
+
applyPersistedUiState(localState, {
|
|
1330
|
+
skipActiveWorkspaceModule: activeWorkspaceModuleOverridePendingRef.current
|
|
1331
|
+
});
|
|
1091
1332
|
lastLoadedUiStateKeyRef.current = uiStateLoadKey;
|
|
1092
1333
|
}
|
|
1093
1334
|
}
|
|
1094
1335
|
finally {
|
|
1095
1336
|
if (currentWorkspaceIdRef.current !== targetWorkspaceId)
|
|
1096
1337
|
return;
|
|
1338
|
+
activeWorkspaceModuleOverridePendingRef.current = false;
|
|
1097
1339
|
skipNextUiStatePersistRef.current = true;
|
|
1098
1340
|
setUiStateReady(true);
|
|
1099
1341
|
}
|
|
@@ -1319,11 +1561,13 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1319
1561
|
}
|
|
1320
1562
|
catch (versionError) {
|
|
1321
1563
|
if (isBootstrapTimeoutError(versionError)) {
|
|
1322
|
-
|
|
1564
|
+
logBootstrapDebug('build_info_timeout', {
|
|
1565
|
+
durationMs: Math.round((typeof performance !== 'undefined' ? performance.now() : Date.now()) - startedAt)
|
|
1566
|
+
});
|
|
1323
1567
|
}
|
|
1324
1568
|
}
|
|
1325
|
-
}, [fetchWithTimeout, isBootstrapTimeoutError
|
|
1326
|
-
const { fetchReferenceData, fetchBootstrapConfig, fetchConfig, fetchWorkspaces, fetchAssigneeOptions, switchWorkspace, resolveWorkspaceAfterAuth, retryBootstrapChecks } = useTaskforceWorkspaceBootstrap({
|
|
1569
|
+
}, [fetchWithTimeout, isBootstrapTimeoutError]);
|
|
1570
|
+
const { fetchReferenceData, fetchBootstrapConfig, fetchConfig, fetchWorkspaces, fetchAssigneeOptions, switchWorkspace, resolveWorkspaceAfterAuth, retryBootstrapChecks, runCloudBootstrap } = useTaskforceWorkspaceBootstrap({
|
|
1327
1571
|
shouldDeferProtectedApiCalls,
|
|
1328
1572
|
shouldBlockProtectedApiCalls,
|
|
1329
1573
|
getWorkspaceRequestState,
|
|
@@ -1363,6 +1607,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1363
1607
|
authSessionResolved,
|
|
1364
1608
|
isAuthenticated,
|
|
1365
1609
|
authUserId,
|
|
1610
|
+
authUserAvatarUrl,
|
|
1366
1611
|
setHydratedWorkspaceRole,
|
|
1367
1612
|
setAssigneeOptions,
|
|
1368
1613
|
setAssigneeOptionsLoaded,
|
|
@@ -1388,6 +1633,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1388
1633
|
showArchive,
|
|
1389
1634
|
taskScope,
|
|
1390
1635
|
settingsSection,
|
|
1636
|
+
workspaceBootstrapPending,
|
|
1391
1637
|
fetchTasks,
|
|
1392
1638
|
fetchArchive,
|
|
1393
1639
|
fetchPlanningEntities,
|
|
@@ -1403,12 +1649,35 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1403
1649
|
useEffect(() => {
|
|
1404
1650
|
if (!configLoaded || workspaceBootstrapPending)
|
|
1405
1651
|
return;
|
|
1406
|
-
const loadKey = `${runtimeMode}:${isAuthenticated ? 'auth' : 'guest'}:${currentWorkspaceId}`;
|
|
1652
|
+
const loadKey = `${runtimeMode}:${isAuthenticated ? 'auth' : 'guest'}:${currentWorkspaceId}:${authUserAvatarUrl || 'no-avatar'}`;
|
|
1407
1653
|
if (assigneeOptionsLoaded && lastAssigneeOptionsLoadKeyRef.current === loadKey)
|
|
1408
1654
|
return;
|
|
1409
1655
|
lastAssigneeOptionsLoadKeyRef.current = loadKey;
|
|
1410
1656
|
void fetchAssigneeOptions();
|
|
1411
|
-
}, [assigneeOptionsLoaded, configLoaded, currentWorkspaceId, fetchAssigneeOptions, isAuthenticated, runtimeMode, workspaceBootstrapPending]);
|
|
1657
|
+
}, [assigneeOptionsLoaded, authUserAvatarUrl, configLoaded, currentWorkspaceId, fetchAssigneeOptions, isAuthenticated, runtimeMode, workspaceBootstrapPending]);
|
|
1658
|
+
useEffect(() => {
|
|
1659
|
+
if (!authSessionResolved || !isAuthenticated)
|
|
1660
|
+
return;
|
|
1661
|
+
const normalizedAuthUserId = String(authUserId || '').trim();
|
|
1662
|
+
if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
|
|
1663
|
+
return;
|
|
1664
|
+
const nextAvatarUrl = String(authUserAvatarUrl || '').trim() || null;
|
|
1665
|
+
setAssigneeOptions((previous) => {
|
|
1666
|
+
let changed = false;
|
|
1667
|
+
const next = previous.map((option) => {
|
|
1668
|
+
if (option.kind !== 'member' || String(option.value || '').trim() !== normalizedAuthUserId)
|
|
1669
|
+
return option;
|
|
1670
|
+
const currentAvatarUrl = typeof option.avatarUrl === 'string' && option.avatarUrl.trim().length > 0
|
|
1671
|
+
? option.avatarUrl.trim()
|
|
1672
|
+
: null;
|
|
1673
|
+
if (currentAvatarUrl === nextAvatarUrl)
|
|
1674
|
+
return option;
|
|
1675
|
+
changed = true;
|
|
1676
|
+
return { ...option, avatarUrl: nextAvatarUrl };
|
|
1677
|
+
});
|
|
1678
|
+
return changed ? next : previous;
|
|
1679
|
+
});
|
|
1680
|
+
}, [authSessionResolved, authUserAvatarUrl, authUserId, isAuthenticated]);
|
|
1412
1681
|
const currentWorkspaceRole = useMemo(() => {
|
|
1413
1682
|
if (workspaceSwitchingEnabled) {
|
|
1414
1683
|
const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
|
|
@@ -1423,44 +1692,86 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1423
1692
|
}, [isAuthenticated]);
|
|
1424
1693
|
const continueAfterCommercialOnboarding = useCallback(async () => {
|
|
1425
1694
|
setBootstrapError(null);
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1695
|
+
const result = await runCloudBootstrap({
|
|
1696
|
+
reason: 'post-plans',
|
|
1697
|
+
ignoreAuthGuard: true,
|
|
1698
|
+
tasksSilent: true,
|
|
1699
|
+
includeDeferredArchive: false
|
|
1700
|
+
});
|
|
1701
|
+
if (!result.authenticated) {
|
|
1702
|
+
return { success: false, destination: 'login', error: result.error || 'Sign in required.', code: result.code || 'AUTH_REQUIRED' };
|
|
1431
1703
|
}
|
|
1432
|
-
if (
|
|
1433
|
-
|
|
1704
|
+
if (!result.success) {
|
|
1705
|
+
return {
|
|
1706
|
+
success: false,
|
|
1707
|
+
destination: 'app',
|
|
1708
|
+
error: result.error || 'Unable to resolve workspace after plan selection.',
|
|
1709
|
+
code: result.code
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
if (result.workspaceSetupRequired) {
|
|
1713
|
+
return { success: true, destination: 'setup' };
|
|
1714
|
+
}
|
|
1715
|
+
return { success: true, destination: 'app' };
|
|
1716
|
+
}, [
|
|
1717
|
+
runCloudBootstrap
|
|
1718
|
+
]);
|
|
1719
|
+
const resolvePostAuthCloudEntry = useCallback(async (options) => {
|
|
1720
|
+
const reason = options?.reason || 'retry';
|
|
1721
|
+
const preferredWorkspaceId = typeof options?.preferredWorkspaceId === 'string' ? options.preferredWorkspaceId.trim() : '';
|
|
1722
|
+
const allowCommercialGate = options?.allowCommercialGate === true;
|
|
1723
|
+
if (preferredWorkspaceId && workspaceSwitchingEnabled) {
|
|
1724
|
+
const authenticated = await checkAuthSession({ force: true });
|
|
1725
|
+
if (!authenticated) {
|
|
1726
|
+
await fetchConfig({ ignoreAuthGuard: true });
|
|
1727
|
+
return { success: false, error: 'Sign in required.', code: 'AUTH_REQUIRED' };
|
|
1728
|
+
}
|
|
1729
|
+
const workspaceResolution = await resolveWorkspaceAfterAuth({ preferredWorkspaceId });
|
|
1434
1730
|
if (!workspaceResolution.success) {
|
|
1435
1731
|
await fetchConfig({ ignoreAuthGuard: true });
|
|
1436
1732
|
return {
|
|
1437
1733
|
success: false,
|
|
1438
|
-
|
|
1439
|
-
error: workspaceResolution.error || 'Unable to resolve workspace after plan selection.',
|
|
1734
|
+
error: workspaceResolution.error || 'Unable to resolve workspace access.',
|
|
1440
1735
|
code: workspaceResolution.code
|
|
1441
1736
|
};
|
|
1442
1737
|
}
|
|
1443
1738
|
if (workspaceResolution.workspaceSetupRequired) {
|
|
1444
1739
|
await fetchConfig({ ignoreAuthGuard: true });
|
|
1445
|
-
return { success: true,
|
|
1740
|
+
return { success: true, workspaceSetupRequired: true };
|
|
1446
1741
|
}
|
|
1447
1742
|
}
|
|
1448
|
-
await
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1743
|
+
const result = await runCloudBootstrap({
|
|
1744
|
+
reason,
|
|
1745
|
+
ignoreAuthGuard: true,
|
|
1746
|
+
tasksSilent: true,
|
|
1747
|
+
includeDeferredArchive: false
|
|
1748
|
+
});
|
|
1749
|
+
if (!result.authenticated) {
|
|
1750
|
+
return { success: false, error: result.error || 'Sign in required.', code: result.code || 'AUTH_REQUIRED' };
|
|
1751
|
+
}
|
|
1752
|
+
if (!result.success) {
|
|
1753
|
+
return {
|
|
1754
|
+
success: false,
|
|
1755
|
+
error: result.error || 'Unable to resolve workspace access.',
|
|
1756
|
+
code: result.code
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
if (result.workspaceSetupRequired) {
|
|
1760
|
+
if (allowCommercialGate) {
|
|
1761
|
+
return { success: true, workspaceSetupRequired: false };
|
|
1762
|
+
}
|
|
1763
|
+
return { success: true, workspaceSetupRequired: true };
|
|
1764
|
+
}
|
|
1765
|
+
if (!allowCommercialGate) {
|
|
1766
|
+
void syncUserGlobalSettings({ preferCloudOnFirstSync: true });
|
|
1767
|
+
}
|
|
1768
|
+
return { success: true, workspaceSetupRequired: false };
|
|
1455
1769
|
}, [
|
|
1456
1770
|
checkAuthSession,
|
|
1457
|
-
fetchBootstrapConfig,
|
|
1458
1771
|
fetchConfig,
|
|
1459
|
-
fetchPlanningEntities,
|
|
1460
|
-
fetchReferenceData,
|
|
1461
|
-
fetchTasks,
|
|
1462
|
-
markBootstrapPhase,
|
|
1463
1772
|
resolveWorkspaceAfterAuth,
|
|
1773
|
+
runCloudBootstrap,
|
|
1774
|
+
syncUserGlobalSettings,
|
|
1464
1775
|
workspaceSwitchingEnabled
|
|
1465
1776
|
]);
|
|
1466
1777
|
const createWorkspace = useCallback(async (name, description) => {
|
|
@@ -1621,30 +1932,34 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1621
1932
|
return { success: false, error: data?.error || 'Sign in failed.', code: data?.code };
|
|
1622
1933
|
}
|
|
1623
1934
|
setError('');
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
return {
|
|
1628
|
-
success: false,
|
|
1629
|
-
error: workspaceResolution.error || 'Unable to resolve workspace after sign in.',
|
|
1630
|
-
code: workspaceResolution.code
|
|
1631
|
-
};
|
|
1632
|
-
}
|
|
1633
|
-
if (workspaceResolution.workspaceSetupRequired) {
|
|
1634
|
-
await fetchConfig({ ignoreAuthGuard: true });
|
|
1635
|
-
return { success: true, workspaceSetupRequired: true };
|
|
1935
|
+
const entry = await resolvePostAuthCloudEntry();
|
|
1936
|
+
if (!entry.success) {
|
|
1937
|
+
return { success: false, error: entry.error || 'Unable to resolve workspace after sign in.', code: entry.code };
|
|
1636
1938
|
}
|
|
1637
|
-
|
|
1638
|
-
fetchConfig({ ignoreAuthGuard: true }),
|
|
1639
|
-
fetchTasks(true, { ignoreAuthGuard: true })
|
|
1640
|
-
]);
|
|
1641
|
-
void syncUserGlobalSettings({ preferCloudOnFirstSync: true });
|
|
1642
|
-
return { success: true };
|
|
1939
|
+
return { success: true, workspaceSetupRequired: entry.workspaceSetupRequired === true };
|
|
1643
1940
|
}
|
|
1644
1941
|
catch {
|
|
1645
1942
|
return { success: false, error: 'Sign in failed.' };
|
|
1646
1943
|
}
|
|
1647
|
-
}, [
|
|
1944
|
+
}, [resolveCloudAuthUrl, cloudAuthConfigured, resolvePostAuthCloudEntry]);
|
|
1945
|
+
/**
|
|
1946
|
+
* Initiates an OAuth login (or invite-claim) flow by redirecting the browser
|
|
1947
|
+
* to the provider's authorisation URL via the server-side start route.
|
|
1948
|
+
*
|
|
1949
|
+
* @param provider - 'google' | 'github' | 'apple'
|
|
1950
|
+
* @param returnTo - Optional relative path to redirect back to after auth (defaults to '/').
|
|
1951
|
+
* @param inviteToken - Optional invite token; when provided the flow uses `invite_claim` mode.
|
|
1952
|
+
*/
|
|
1953
|
+
const beginOAuthLogin = useCallback((provider, returnTo, inviteToken) => {
|
|
1954
|
+
const sanitizedReturnTo = returnTo && /^\/[A-Za-z0-9_\-./?=&%]*$/.test(returnTo)
|
|
1955
|
+
? returnTo
|
|
1956
|
+
: '/';
|
|
1957
|
+
let path = `/api/taskforce/auth/oauth/${encodeURIComponent(provider)}/start?return_to=${encodeURIComponent(sanitizedReturnTo)}`;
|
|
1958
|
+
if (inviteToken) {
|
|
1959
|
+
path += `&invite_token=${encodeURIComponent(inviteToken)}`;
|
|
1960
|
+
}
|
|
1961
|
+
window.location.href = resolveCloudAuthUrl(path);
|
|
1962
|
+
}, [resolveCloudAuthUrl]);
|
|
1648
1963
|
const registerWithCredentials = useCallback(async (email, password, options) => {
|
|
1649
1964
|
if (!cloudAuthConfigured) {
|
|
1650
1965
|
return { success: false, error: 'Cloud auth endpoint is not configured.' };
|
|
@@ -1694,32 +2009,42 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1694
2009
|
const verificationRequired = Boolean(data?.verificationRequired);
|
|
1695
2010
|
if (!verificationRequired) {
|
|
1696
2011
|
setError('');
|
|
1697
|
-
await checkAuthSession({ force: true });
|
|
1698
|
-
const workspaceResolution = await resolveWorkspaceAfterAuth();
|
|
1699
|
-
if (!workspaceResolution.success) {
|
|
1700
|
-
return {
|
|
1701
|
-
success: false,
|
|
1702
|
-
error: workspaceResolution.error || 'Unable to resolve workspace after registration.',
|
|
1703
|
-
code: workspaceResolution.code
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
2012
|
const hasCommercialOnboardingGate = Boolean(planSelectionRequired
|
|
1707
2013
|
|| checkoutPending
|
|
1708
2014
|
|| commercialState === 'pending_plan_selection'
|
|
1709
2015
|
|| commercialState === 'checkout_pending');
|
|
1710
|
-
if (
|
|
1711
|
-
await
|
|
2016
|
+
if (hasCommercialOnboardingGate) {
|
|
2017
|
+
const authenticated = await checkAuthSession({ force: true });
|
|
2018
|
+
if (!authenticated) {
|
|
2019
|
+
return {
|
|
2020
|
+
success: false,
|
|
2021
|
+
error: 'Unable to resolve workspace after registration.',
|
|
2022
|
+
code: 'AUTH_REQUIRED'
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
return {
|
|
2026
|
+
success: true,
|
|
2027
|
+
workspaceSetupRequired: false,
|
|
2028
|
+
planSelectionRequired,
|
|
2029
|
+
checkoutPending,
|
|
2030
|
+
commercialState,
|
|
2031
|
+
verificationRequired,
|
|
2032
|
+
verificationToken: typeof data?.verificationToken === 'string' ? data.verificationToken : undefined,
|
|
2033
|
+
emailSent: data?.emailSent !== false,
|
|
2034
|
+
emailError: typeof data?.emailError === 'string' ? data.emailError : undefined
|
|
2035
|
+
};
|
|
1712
2036
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
2037
|
+
const entry = await resolvePostAuthCloudEntry({ allowCommercialGate: false });
|
|
2038
|
+
if (!entry.success) {
|
|
2039
|
+
return {
|
|
2040
|
+
success: false,
|
|
2041
|
+
error: entry.error || 'Unable to resolve workspace after registration.',
|
|
2042
|
+
code: entry.code
|
|
2043
|
+
};
|
|
1719
2044
|
}
|
|
1720
2045
|
return {
|
|
1721
2046
|
success: true,
|
|
1722
|
-
workspaceSetupRequired:
|
|
2047
|
+
workspaceSetupRequired: entry.workspaceSetupRequired === true,
|
|
1723
2048
|
planSelectionRequired,
|
|
1724
2049
|
checkoutPending,
|
|
1725
2050
|
commercialState,
|
|
@@ -1744,7 +2069,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1744
2069
|
catch {
|
|
1745
2070
|
return { success: false, error: 'Create account failed.' };
|
|
1746
2071
|
}
|
|
1747
|
-
}, [checkAuthSession,
|
|
2072
|
+
}, [checkAuthSession, resolveCloudAuthUrl, cloudAuthConfigured, resolvePostAuthCloudEntry]);
|
|
1748
2073
|
const updateCurrentUserProfile = useCallback(async (input) => {
|
|
1749
2074
|
if (!cloudAuthConfigured) {
|
|
1750
2075
|
return { success: false, error: 'Cloud auth endpoint is not configured.' };
|
|
@@ -1925,7 +2250,9 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1925
2250
|
email: typeof data?.email === 'string' ? data.email : undefined,
|
|
1926
2251
|
workspaceId: typeof data?.workspaceId === 'string' ? data.workspaceId : undefined,
|
|
1927
2252
|
inviteeKind: data?.inviteeKind === 'existing_user' ? 'existing_user' : 'new_user',
|
|
1928
|
-
passwordRequired: data?.passwordRequired === true
|
|
2253
|
+
passwordRequired: data?.passwordRequired === true,
|
|
2254
|
+
inviteeState: data?.inviteeState === 'existing_account' ? 'existing_account' : 'pending_setup',
|
|
2255
|
+
availableMethods: Array.isArray(data?.availableMethods) ? data.availableMethods : []
|
|
1929
2256
|
};
|
|
1930
2257
|
}
|
|
1931
2258
|
catch {
|
|
@@ -1953,30 +2280,30 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1953
2280
|
return { success: false, error: data?.error || 'Invite acceptance failed.', code: data?.code };
|
|
1954
2281
|
}
|
|
1955
2282
|
setError('');
|
|
1956
|
-
await checkAuthSession({ force: true });
|
|
1957
2283
|
const preferredWorkspaceId = typeof data?.workspaceId === 'string' ? data.workspaceId : null;
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2284
|
+
if (preferredWorkspaceId && workspaceSwitchingEnabled) {
|
|
2285
|
+
const switched = await switchWorkspace(preferredWorkspaceId, { hydrate: false });
|
|
2286
|
+
if (!switched.success) {
|
|
2287
|
+
return {
|
|
2288
|
+
success: false,
|
|
2289
|
+
error: switched.error || 'Unable to resolve workspace after invite acceptance.',
|
|
2290
|
+
code: switched.code
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
1965
2293
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
2294
|
+
const entry = await resolvePostAuthCloudEntry();
|
|
2295
|
+
if (!entry.success) {
|
|
2296
|
+
return { success: false, error: entry.error || 'Unable to resolve workspace after invite acceptance.', code: entry.code };
|
|
1968
2297
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
void syncUserGlobalSettings({ preferCloudOnFirstSync: true });
|
|
1974
|
-
return { success: true };
|
|
2298
|
+
if (preferredWorkspaceId && currentWorkspaceIdRef.current !== preferredWorkspaceId) {
|
|
2299
|
+
commitCurrentWorkspaceId(preferredWorkspaceId, { clearExplicitSelection: false });
|
|
2300
|
+
}
|
|
2301
|
+
return { success: true, workspaceSetupRequired: entry.workspaceSetupRequired === true };
|
|
1975
2302
|
}
|
|
1976
2303
|
catch {
|
|
1977
2304
|
return { success: false, error: 'Invite acceptance failed.' };
|
|
1978
2305
|
}
|
|
1979
|
-
}, [
|
|
2306
|
+
}, [commitCurrentWorkspaceId, resolveCloudAuthUrl, cloudAuthConfigured, currentWorkspaceIdRef, resolvePostAuthCloudEntry, switchWorkspace, workspaceSwitchingEnabled]);
|
|
1980
2307
|
const joinInviteWithToken = useCallback(async (token) => {
|
|
1981
2308
|
if (!cloudAuthConfigured) {
|
|
1982
2309
|
return { success: false, error: 'Cloud auth endpoint is not configured.' };
|
|
@@ -1997,30 +2324,93 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
1997
2324
|
return { success: false, error: data?.error || 'Invite join failed.', code: data?.code };
|
|
1998
2325
|
}
|
|
1999
2326
|
setError('');
|
|
2000
|
-
await checkAuthSession({ force: true });
|
|
2001
2327
|
const preferredWorkspaceId = typeof data?.workspaceId === 'string' ? data.workspaceId : null;
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2328
|
+
if (preferredWorkspaceId && workspaceSwitchingEnabled) {
|
|
2329
|
+
const switched = await switchWorkspace(preferredWorkspaceId, { hydrate: false });
|
|
2330
|
+
if (!switched.success) {
|
|
2331
|
+
return {
|
|
2332
|
+
success: false,
|
|
2333
|
+
error: switched.error || 'Unable to resolve workspace after invite join.',
|
|
2334
|
+
code: switched.code
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2009
2337
|
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2338
|
+
const entry = await resolvePostAuthCloudEntry();
|
|
2339
|
+
if (!entry.success) {
|
|
2340
|
+
return { success: false, error: entry.error || 'Unable to resolve workspace after invite join.', code: entry.code };
|
|
2012
2341
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
void syncUserGlobalSettings({ preferCloudOnFirstSync: true });
|
|
2018
|
-
return { success: true };
|
|
2342
|
+
if (preferredWorkspaceId && currentWorkspaceIdRef.current !== preferredWorkspaceId) {
|
|
2343
|
+
commitCurrentWorkspaceId(preferredWorkspaceId, { clearExplicitSelection: false });
|
|
2344
|
+
}
|
|
2345
|
+
return { success: true, workspaceSetupRequired: entry.workspaceSetupRequired === true };
|
|
2019
2346
|
}
|
|
2020
2347
|
catch {
|
|
2021
2348
|
return { success: false, error: 'Invite join failed.' };
|
|
2022
2349
|
}
|
|
2023
|
-
}, [
|
|
2350
|
+
}, [commitCurrentWorkspaceId, resolveCloudAuthUrl, cloudAuthConfigured, currentWorkspaceIdRef, resolvePostAuthCloudEntry, switchWorkspace, workspaceSwitchingEnabled]);
|
|
2351
|
+
// ─── Login method management ────────────────────────────────────────────────
|
|
2352
|
+
const fetchLoginMethods = useCallback(async () => {
|
|
2353
|
+
if (!cloudAuthConfigured)
|
|
2354
|
+
return { success: false, error: 'Cloud auth not configured.' };
|
|
2355
|
+
try {
|
|
2356
|
+
const res = await fetch(resolveCloudAuthUrl('/api/taskforce/account/login-methods'), {
|
|
2357
|
+
credentials: 'include'
|
|
2358
|
+
});
|
|
2359
|
+
const data = await res.json().catch(() => ({}));
|
|
2360
|
+
if (!res.ok || !data?.success) {
|
|
2361
|
+
return { success: false, error: data?.error || 'Failed to fetch login methods.' };
|
|
2362
|
+
}
|
|
2363
|
+
return { success: true, methods: Array.isArray(data?.methods) ? data.methods : [] };
|
|
2364
|
+
}
|
|
2365
|
+
catch {
|
|
2366
|
+
return { success: false, error: 'Failed to fetch login methods.' };
|
|
2367
|
+
}
|
|
2368
|
+
}, [resolveCloudAuthUrl, cloudAuthConfigured]);
|
|
2369
|
+
const unlinkLoginMethod = useCallback(async (provider) => {
|
|
2370
|
+
if (!cloudAuthConfigured)
|
|
2371
|
+
return { success: false, error: 'Cloud auth not configured.' };
|
|
2372
|
+
try {
|
|
2373
|
+
const res = await fetch(resolveCloudAuthUrl(`/api/taskforce/account/login-methods/${encodeURIComponent(provider)}`), {
|
|
2374
|
+
method: 'DELETE',
|
|
2375
|
+
credentials: 'include'
|
|
2376
|
+
});
|
|
2377
|
+
const data = await res.json().catch(() => ({}));
|
|
2378
|
+
if (!res.ok || !data?.success) {
|
|
2379
|
+
return { success: false, error: data?.error || 'Unlink failed.', code: data?.code };
|
|
2380
|
+
}
|
|
2381
|
+
return { success: true };
|
|
2382
|
+
}
|
|
2383
|
+
catch {
|
|
2384
|
+
return { success: false, error: 'Unlink failed.' };
|
|
2385
|
+
}
|
|
2386
|
+
}, [resolveCloudAuthUrl, cloudAuthConfigured]);
|
|
2387
|
+
const addPasswordToAccount = useCallback(async (password) => {
|
|
2388
|
+
if (!cloudAuthConfigured)
|
|
2389
|
+
return { success: false, error: 'Cloud auth not configured.' };
|
|
2390
|
+
try {
|
|
2391
|
+
const res = await fetch(resolveCloudAuthUrl('/api/taskforce/account/login-methods/password'), {
|
|
2392
|
+
method: 'POST',
|
|
2393
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2394
|
+
credentials: 'include',
|
|
2395
|
+
body: JSON.stringify({ password })
|
|
2396
|
+
});
|
|
2397
|
+
const data = await res.json().catch(() => ({}));
|
|
2398
|
+
if (!res.ok || !data?.success) {
|
|
2399
|
+
return { success: false, error: data?.error || 'Failed to add password.', code: data?.code };
|
|
2400
|
+
}
|
|
2401
|
+
return { success: true };
|
|
2402
|
+
}
|
|
2403
|
+
catch {
|
|
2404
|
+
return { success: false, error: 'Failed to add password.' };
|
|
2405
|
+
}
|
|
2406
|
+
}, [resolveCloudAuthUrl, cloudAuthConfigured]);
|
|
2407
|
+
const beginOAuthLink = useCallback((provider, returnTo) => {
|
|
2408
|
+
const sanitizedReturnTo = returnTo && /^\/[A-Za-z0-9_\-./?=&%]*$/.test(returnTo)
|
|
2409
|
+
? returnTo
|
|
2410
|
+
: '/';
|
|
2411
|
+
const path = `/api/taskforce/auth/oauth/${encodeURIComponent(provider)}/start?flow=link&return_to=${encodeURIComponent(sanitizedReturnTo)}`;
|
|
2412
|
+
window.location.href = resolveCloudAuthUrl(path);
|
|
2413
|
+
}, [resolveCloudAuthUrl]);
|
|
2024
2414
|
const resetCloudLogoutClientState = useCallback(() => {
|
|
2025
2415
|
abortWorkspaceScopedRequests();
|
|
2026
2416
|
referenceDataRequestRef.current = null;
|
|
@@ -2039,6 +2429,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2039
2429
|
setArchivedTasks([]);
|
|
2040
2430
|
setInitiatives([]);
|
|
2041
2431
|
setWorkstreams([]);
|
|
2432
|
+
setPlanningBootstrapTaskSummaries([]);
|
|
2042
2433
|
setLoadingTasks(false);
|
|
2043
2434
|
if (cloudRealtimeFollowUpRefreshTimerRef.current !== null && typeof window !== 'undefined') {
|
|
2044
2435
|
window.clearTimeout(cloudRealtimeFollowUpRefreshTimerRef.current);
|
|
@@ -2149,6 +2540,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2149
2540
|
throw new Error(data?.error || `Failed to create initiative (${res.status})`);
|
|
2150
2541
|
}
|
|
2151
2542
|
await fetchPlanningEntities();
|
|
2543
|
+
setInitiatives((current) => upsertEntityById(current, data));
|
|
2152
2544
|
return data;
|
|
2153
2545
|
}, [fetchPlanningEntities]);
|
|
2154
2546
|
const updateInitiative = useCallback(async (id, payload) => {
|
|
@@ -2162,6 +2554,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2162
2554
|
throw new Error(data?.error || `Failed to update initiative (${res.status})`);
|
|
2163
2555
|
}
|
|
2164
2556
|
await fetchPlanningEntities();
|
|
2557
|
+
setInitiatives((current) => upsertEntityById(current, data));
|
|
2165
2558
|
return data;
|
|
2166
2559
|
}, [fetchPlanningEntities]);
|
|
2167
2560
|
const archiveInitiative = useCallback(async (id) => {
|
|
@@ -2173,6 +2566,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2173
2566
|
throw new Error(data?.error || `Failed to archive initiative (${res.status})`);
|
|
2174
2567
|
}
|
|
2175
2568
|
await fetchPlanningEntities();
|
|
2569
|
+
setInitiatives((current) => upsertEntityById(current, data));
|
|
2176
2570
|
return data;
|
|
2177
2571
|
}, [fetchPlanningEntities]);
|
|
2178
2572
|
const unarchiveInitiative = useCallback(async (id) => {
|
|
@@ -2184,6 +2578,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2184
2578
|
throw new Error(data?.error || `Failed to unarchive initiative (${res.status})`);
|
|
2185
2579
|
}
|
|
2186
2580
|
await fetchPlanningEntities();
|
|
2581
|
+
setInitiatives((current) => upsertEntityById(current, data));
|
|
2187
2582
|
return data;
|
|
2188
2583
|
}, [fetchPlanningEntities]);
|
|
2189
2584
|
const createWorkstream = useCallback(async (payload) => {
|
|
@@ -2197,6 +2592,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2197
2592
|
throw new Error(data?.error || `Failed to create workstream (${res.status})`);
|
|
2198
2593
|
}
|
|
2199
2594
|
await fetchPlanningEntities();
|
|
2595
|
+
setWorkstreams((current) => upsertEntityById(current, data));
|
|
2200
2596
|
return data;
|
|
2201
2597
|
}, [fetchPlanningEntities]);
|
|
2202
2598
|
const updateWorkstream = useCallback(async (id, payload) => {
|
|
@@ -2210,6 +2606,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2210
2606
|
throw new Error(data?.error || `Failed to update workstream (${res.status})`);
|
|
2211
2607
|
}
|
|
2212
2608
|
await fetchPlanningEntities();
|
|
2609
|
+
setWorkstreams((current) => upsertEntityById(current, data));
|
|
2213
2610
|
return data;
|
|
2214
2611
|
}, [fetchPlanningEntities]);
|
|
2215
2612
|
const archiveWorkstream = useCallback(async (id) => {
|
|
@@ -2221,6 +2618,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2221
2618
|
throw new Error(data?.error || `Failed to archive workstream (${res.status})`);
|
|
2222
2619
|
}
|
|
2223
2620
|
await fetchPlanningEntities();
|
|
2621
|
+
setWorkstreams((current) => upsertEntityById(current, data));
|
|
2224
2622
|
return data;
|
|
2225
2623
|
}, [fetchPlanningEntities]);
|
|
2226
2624
|
const unarchiveWorkstream = useCallback(async (id) => {
|
|
@@ -2232,6 +2630,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2232
2630
|
throw new Error(data?.error || `Failed to unarchive workstream (${res.status})`);
|
|
2233
2631
|
}
|
|
2234
2632
|
await fetchPlanningEntities();
|
|
2633
|
+
setWorkstreams((current) => upsertEntityById(current, data));
|
|
2235
2634
|
return data;
|
|
2236
2635
|
}, [fetchPlanningEntities]);
|
|
2237
2636
|
// Initialize auth once, then bootstrap protected data once auth gates are settled.
|
|
@@ -2248,6 +2647,38 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2248
2647
|
return;
|
|
2249
2648
|
void checkAuthSession();
|
|
2250
2649
|
}, [runtimeConfigReady, checkAuthSession]);
|
|
2650
|
+
// Fetch available OAuth providers once we know the auth endpoint.
|
|
2651
|
+
// Uses a short timeout so an unreachable auth server doesn't hold open
|
|
2652
|
+
// browser connections and slow down subsequent requests on the same origin.
|
|
2653
|
+
useEffect(() => {
|
|
2654
|
+
if (!cloudAuthConfigured)
|
|
2655
|
+
return;
|
|
2656
|
+
let cancelled = false;
|
|
2657
|
+
const controller = new AbortController();
|
|
2658
|
+
const timerId = typeof window !== 'undefined'
|
|
2659
|
+
? window.setTimeout(() => controller.abort(), 5000)
|
|
2660
|
+
: null;
|
|
2661
|
+
fetch(resolveCloudAuthUrl('/api/taskforce/auth/providers'), {
|
|
2662
|
+
credentials: 'include',
|
|
2663
|
+
signal: controller.signal
|
|
2664
|
+
})
|
|
2665
|
+
.then(r => r.ok ? r.json() : null)
|
|
2666
|
+
.then(data => {
|
|
2667
|
+
if (!cancelled && Array.isArray(data?.providers)) {
|
|
2668
|
+
setAvailableAuthProviders(data.providers);
|
|
2669
|
+
}
|
|
2670
|
+
})
|
|
2671
|
+
.catch(() => { })
|
|
2672
|
+
.finally(() => {
|
|
2673
|
+
if (timerId !== null && typeof window !== 'undefined') {
|
|
2674
|
+
window.clearTimeout(timerId);
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
return () => {
|
|
2678
|
+
cancelled = true;
|
|
2679
|
+
controller.abort();
|
|
2680
|
+
};
|
|
2681
|
+
}, [cloudAuthConfigured, resolveCloudAuthUrl]);
|
|
2251
2682
|
// Keep local runtime auth state aligned with cloud session changes
|
|
2252
2683
|
// (e.g. user signs in/out in another tab or cloud-hosted app).
|
|
2253
2684
|
useEffect(() => {
|
|
@@ -2271,109 +2702,6 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2271
2702
|
window.clearInterval(intervalId);
|
|
2272
2703
|
};
|
|
2273
2704
|
}, [runtimeMode, shouldProbeCloudAuth, checkAuthSession]);
|
|
2274
|
-
useEffect(() => {
|
|
2275
|
-
if (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls)
|
|
2276
|
-
return;
|
|
2277
|
-
if (hasBootstrappedDataRef.current)
|
|
2278
|
-
return;
|
|
2279
|
-
hasBootstrappedDataRef.current = true;
|
|
2280
|
-
void (async () => {
|
|
2281
|
-
if (runtimeMode === 'cloud' && authSessionResolved && isAuthenticated) {
|
|
2282
|
-
const workspaceResolution = await resolveWorkspaceAfterAuth();
|
|
2283
|
-
if (!workspaceResolution.success || workspaceResolution.workspaceSetupRequired) {
|
|
2284
|
-
await fetchBootstrapConfig();
|
|
2285
|
-
void fetchReferenceData();
|
|
2286
|
-
return;
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
await fetchBootstrapConfig();
|
|
2290
|
-
await Promise.all([
|
|
2291
|
-
(async () => {
|
|
2292
|
-
const startedAt = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
2293
|
-
await fetchTasks(false);
|
|
2294
|
-
logBootstrapDebug('bootstrap_tasks_loaded', {
|
|
2295
|
-
durationMs: Math.round((typeof performance !== 'undefined' ? performance.now() : Date.now()) - startedAt)
|
|
2296
|
-
});
|
|
2297
|
-
})(),
|
|
2298
|
-
fetchReferenceData(),
|
|
2299
|
-
fetchPlanningEntities()
|
|
2300
|
-
]);
|
|
2301
|
-
if (typeof window !== 'undefined') {
|
|
2302
|
-
if (archiveBootstrapTimerRef.current !== null) {
|
|
2303
|
-
window.clearTimeout(archiveBootstrapTimerRef.current);
|
|
2304
|
-
}
|
|
2305
|
-
archiveBootstrapTimerRef.current = window.setTimeout(() => {
|
|
2306
|
-
archiveBootstrapTimerRef.current = null;
|
|
2307
|
-
const startedAt = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
2308
|
-
void fetchArchive(true).then(() => {
|
|
2309
|
-
logBootstrapDebug('bootstrap_archive_loaded_deferred', {
|
|
2310
|
-
durationMs: Math.round((typeof performance !== 'undefined' ? performance.now() : Date.now()) - startedAt)
|
|
2311
|
-
});
|
|
2312
|
-
});
|
|
2313
|
-
}, 1500);
|
|
2314
|
-
}
|
|
2315
|
-
})();
|
|
2316
|
-
}, [
|
|
2317
|
-
shouldDeferProtectedApiCalls,
|
|
2318
|
-
shouldBlockProtectedApiCalls,
|
|
2319
|
-
runtimeMode,
|
|
2320
|
-
authSessionResolved,
|
|
2321
|
-
isAuthenticated,
|
|
2322
|
-
resolveWorkspaceAfterAuth,
|
|
2323
|
-
fetchBootstrapConfig,
|
|
2324
|
-
fetchTasks,
|
|
2325
|
-
fetchArchive,
|
|
2326
|
-
fetchReferenceData,
|
|
2327
|
-
fetchPlanningEntities
|
|
2328
|
-
]);
|
|
2329
|
-
useEffect(() => {
|
|
2330
|
-
if (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls)
|
|
2331
|
-
return;
|
|
2332
|
-
if (workspaceBootstrapPending)
|
|
2333
|
-
return;
|
|
2334
|
-
if (workspaceSelectionScope === WORKSPACE_BOOTSTRAP_SCOPE)
|
|
2335
|
-
return;
|
|
2336
|
-
if (!hasBootstrappedDataRef.current)
|
|
2337
|
-
return;
|
|
2338
|
-
const loadKey = `${runtimeMode}:${currentWorkspaceId}:${workspaceSelectionScope}`;
|
|
2339
|
-
if (lastConfigRefreshKeyRef.current === loadKey)
|
|
2340
|
-
return;
|
|
2341
|
-
lastConfigRefreshKeyRef.current = loadKey;
|
|
2342
|
-
void fetchConfig();
|
|
2343
|
-
}, [
|
|
2344
|
-
currentWorkspaceId,
|
|
2345
|
-
shouldDeferProtectedApiCalls,
|
|
2346
|
-
shouldBlockProtectedApiCalls,
|
|
2347
|
-
workspaceBootstrapPending,
|
|
2348
|
-
runtimeMode,
|
|
2349
|
-
workspaceSelectionScope,
|
|
2350
|
-
fetchConfig
|
|
2351
|
-
]);
|
|
2352
|
-
useEffect(() => {
|
|
2353
|
-
if (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls)
|
|
2354
|
-
return;
|
|
2355
|
-
if (workspaceBootstrapPending)
|
|
2356
|
-
return;
|
|
2357
|
-
const loadKey = currentWorkspaceId;
|
|
2358
|
-
if (lastWorkspaceSyncStateLoadKeyRef.current === loadKey)
|
|
2359
|
-
return;
|
|
2360
|
-
lastWorkspaceSyncStateLoadKeyRef.current = loadKey;
|
|
2361
|
-
void loadWorkspaceSyncState();
|
|
2362
|
-
}, [shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls, currentWorkspaceId, loadWorkspaceSyncState, workspaceBootstrapPending]);
|
|
2363
|
-
useEffect(() => {
|
|
2364
|
-
if (runtimeMode !== 'cloud' || !authSessionResolved || !isAuthenticated) {
|
|
2365
|
-
if (runtimeMode !== 'cloud') {
|
|
2366
|
-
setAvailableWorkspaces([]);
|
|
2367
|
-
}
|
|
2368
|
-
workspaceListHydratedRef.current = false;
|
|
2369
|
-
return;
|
|
2370
|
-
}
|
|
2371
|
-
if (workspaceBootstrapPending)
|
|
2372
|
-
return;
|
|
2373
|
-
if (!hasBootstrappedDataRef.current || workspaceListHydratedRef.current)
|
|
2374
|
-
return;
|
|
2375
|
-
fetchWorkspaces();
|
|
2376
|
-
}, [runtimeMode, authSessionResolved, isAuthenticated, fetchWorkspaces, workspaceBootstrapPending]);
|
|
2377
2705
|
// Report task count to parent
|
|
2378
2706
|
useEffect(() => {
|
|
2379
2707
|
if (onTaskCountChange) {
|
|
@@ -2539,6 +2867,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2539
2867
|
return;
|
|
2540
2868
|
lastTaskSurfaceLoadKeyRef.current = loadKey;
|
|
2541
2869
|
fetchTasks();
|
|
2870
|
+
fetchPlanningEntities();
|
|
2542
2871
|
if (showArchive || taskScope === 'archived') {
|
|
2543
2872
|
fetchArchive(true);
|
|
2544
2873
|
}
|
|
@@ -2565,6 +2894,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2565
2894
|
showArchive,
|
|
2566
2895
|
taskScope,
|
|
2567
2896
|
fetchTasks,
|
|
2897
|
+
fetchPlanningEntities,
|
|
2568
2898
|
fetchArchive,
|
|
2569
2899
|
fetchDeletedTasks,
|
|
2570
2900
|
fetchConfig,
|
|
@@ -3023,6 +3353,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
3023
3353
|
workspaceCloudSyncEnabled,
|
|
3024
3354
|
saveWorkspaceCloudSyncSettings,
|
|
3025
3355
|
applyWorkspaceSyncStateSnapshot,
|
|
3356
|
+
bootstrapState,
|
|
3026
3357
|
authSessionResolved,
|
|
3027
3358
|
workspaceBootstrapPending,
|
|
3028
3359
|
bootstrapPhase,
|
|
@@ -3039,6 +3370,12 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
3039
3370
|
deleteWorkspace,
|
|
3040
3371
|
switchWorkspace,
|
|
3041
3372
|
loginWithCredentials,
|
|
3373
|
+
beginOAuthLogin,
|
|
3374
|
+
availableAuthProviders,
|
|
3375
|
+
fetchLoginMethods,
|
|
3376
|
+
unlinkLoginMethod,
|
|
3377
|
+
addPasswordToAccount,
|
|
3378
|
+
beginOAuthLink,
|
|
3042
3379
|
registerWithCredentials,
|
|
3043
3380
|
updateCurrentUserProfile,
|
|
3044
3381
|
requestEmailVerification,
|
|
@@ -3071,6 +3408,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
3071
3408
|
archivedTasks,
|
|
3072
3409
|
initiatives,
|
|
3073
3410
|
workstreams,
|
|
3411
|
+
planningBootstrapTaskSummaries,
|
|
3074
3412
|
deletedTasks,
|
|
3075
3413
|
activeCategories,
|
|
3076
3414
|
activeTypes,
|
|
@@ -3093,6 +3431,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
3093
3431
|
setFilterAssignees,
|
|
3094
3432
|
filterTaxonomies,
|
|
3095
3433
|
setFilterTaxonomies,
|
|
3434
|
+
hasInitedFilters,
|
|
3096
3435
|
sortBy,
|
|
3097
3436
|
setSortBy,
|
|
3098
3437
|
sortOrder,
|