@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { useMemo, useCallback, useState, useEffect, useRef,
|
|
2
|
+
import React, { useMemo, useCallback, useState, useEffect, useRef, Suspense } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
5
|
-
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ClockAlert, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
|
|
5
|
+
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ClockAlert, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow, Plug } from 'lucide-react';
|
|
6
6
|
import { STATUS_OPTIONS, getStatusDefinition } from '../../utils/constants';
|
|
7
7
|
import styles from '../../Taskforce.module.css';
|
|
8
8
|
import shellStyles from './WorkspaceShell.module.css';
|
|
@@ -12,9 +12,11 @@ import folderBrowserStyles from './FolderBrowser.module.css';
|
|
|
12
12
|
import modalStyles from '../ui/Modal.module.css';
|
|
13
13
|
import { CreateWorkspaceConfirmModal } from './standalone/modals/CreateWorkspaceConfirmModal';
|
|
14
14
|
import { AccountHubModal } from './standalone/modals/AccountHubModal';
|
|
15
|
+
import { AvatarImageManagerModal } from './standalone/modals/AvatarImageManagerModal';
|
|
15
16
|
import { EditProfileModal } from './standalone/modals/EditProfileModal';
|
|
16
17
|
import { HelpModal } from './standalone/modals/HelpModal';
|
|
17
18
|
import { ScheduleWarningModal } from './standalone/modals/ScheduleWarningModal';
|
|
19
|
+
import { SyncEnableWarningModal } from './standalone/modals/SyncEnableWarningModal';
|
|
18
20
|
import { SyncStatusModal } from './standalone/modals/SyncStatusModal';
|
|
19
21
|
import { TeamManagementModal } from './standalone/modals/TeamManagementModal';
|
|
20
22
|
import { Modal } from '../ui/Modal';
|
|
@@ -27,14 +29,17 @@ import { resolveDraftDefaultsForGrouping } from '../../utils/taskDraftDefaults';
|
|
|
27
29
|
import { formatDate } from '../../utils/formatting';
|
|
28
30
|
import { resolveHeaderWorkspaceSyncPresentation } from '../../utils/workspaceSyncPresentation';
|
|
29
31
|
import { getTaskReferenceLabel, parseTaskReference } from '../../utils/taskReferences';
|
|
32
|
+
import { normalizeTaskSearchQuery, taskMatchesSearch } from '../../utils/taskSearch';
|
|
30
33
|
import { formatInitiativeReference, formatWorkstreamReference, parseWorkstreamReference, resolveInitiativeReference } from '../../utils/planningReferences';
|
|
31
34
|
import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
|
|
32
35
|
import { getImageReferenceLabel } from '../../utils/imageReferences';
|
|
36
|
+
import { isPlanningEntityVisibleForScope, scopePlanningSummaries } from './planningScope';
|
|
37
|
+
import { buildReferenceMismatchSummaries, buildSyncDiagnosticsSummary, buildSyncEventRows, getActiveReferenceMismatchCount, resolveSyncStageLabel, resolveSyncStatusMeta, } from '../../utils/syncStatusPresentation';
|
|
33
38
|
import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
|
|
34
39
|
import { getLocale, t } from '../../localization';
|
|
35
40
|
import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
|
|
36
41
|
import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
|
|
37
|
-
import {
|
|
42
|
+
import { prepareAvatarUploadFile } from '../../utils/avatarUpload';
|
|
38
43
|
import { resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
|
|
39
44
|
import { AppShellHeader } from './AppShellHeader';
|
|
40
45
|
import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
|
|
@@ -47,6 +52,9 @@ import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider
|
|
|
47
52
|
import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
|
|
48
53
|
import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
|
|
49
54
|
import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement } from './teamManagementAccess';
|
|
55
|
+
import { useRecentSyncEvents } from '../../hooks/sync/useRecentSyncEvents';
|
|
56
|
+
import { useSyncStatusActions } from '../../hooks/sync/useSyncStatusActions';
|
|
57
|
+
import { useSyncStatusControls } from '../../hooks/sync/useSyncStatusControls';
|
|
50
58
|
import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
|
|
51
59
|
import { AGENTS_WORKSPACE_FEATURE_KEY, ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, INITIATIVES_WORKSPACE_FEATURE_KEY, WORKFLOWS_WORKSPACE_FEATURE_KEY, resolveFeatureAccess, } from '../../runtime/appGates';
|
|
52
60
|
// Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
|
|
@@ -60,72 +68,6 @@ const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m
|
|
|
60
68
|
const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
|
|
61
69
|
const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
|
|
62
70
|
const resolveSameOriginTaskforcePath = (path) => path;
|
|
63
|
-
function getSyncEventStableKey(event, index = 0) {
|
|
64
|
-
const explicitId = String(event.id ?? '').trim();
|
|
65
|
-
if (explicitId)
|
|
66
|
-
return `id:${explicitId}`;
|
|
67
|
-
return [
|
|
68
|
-
String(event.occurredAt || '').trim() || 'unknown-time',
|
|
69
|
-
String(event.eventType || '').trim() || 'sync',
|
|
70
|
-
String(event.status || '').trim() || 'unknown-status',
|
|
71
|
-
String(event.statusCode ?? ''),
|
|
72
|
-
String(event.changeCount ?? ''),
|
|
73
|
-
String(event.requestMs ?? ''),
|
|
74
|
-
String(event.errorMessage || '').trim(),
|
|
75
|
-
JSON.stringify(event.details || null),
|
|
76
|
-
String(index)
|
|
77
|
-
].join('|');
|
|
78
|
-
}
|
|
79
|
-
function mergeRecentSyncEvents(previous, incoming, limit = 15) {
|
|
80
|
-
const merged = new Map();
|
|
81
|
-
for (const [index, event] of previous.entries()) {
|
|
82
|
-
merged.set(getSyncEventStableKey(event, index), event);
|
|
83
|
-
}
|
|
84
|
-
for (const [index, event] of incoming.entries()) {
|
|
85
|
-
merged.set(getSyncEventStableKey(event, index), event);
|
|
86
|
-
}
|
|
87
|
-
return Array.from(merged.values())
|
|
88
|
-
.sort((left, right) => String(right.occurredAt || '').localeCompare(String(left.occurredAt || '')))
|
|
89
|
-
.slice(0, Math.max(1, limit));
|
|
90
|
-
}
|
|
91
|
-
function areSyncEventListsEqual(left, right) {
|
|
92
|
-
if (left.length !== right.length)
|
|
93
|
-
return false;
|
|
94
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
95
|
-
if (getSyncEventStableKey(left[index], index) !== getSyncEventStableKey(right[index], index)) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
function getActiveReferenceMismatchCount(events) {
|
|
102
|
-
let count = 0;
|
|
103
|
-
for (const event of events) {
|
|
104
|
-
const message = String(event.errorMessage || '').toLowerCase();
|
|
105
|
-
if (message.includes('reference number mismatch')) {
|
|
106
|
-
count += 1;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (String(event.status || '').toLowerCase() === 'success') {
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return count;
|
|
114
|
-
}
|
|
115
|
-
function getActiveReferenceMismatchEvents(events) {
|
|
116
|
-
const mismatches = [];
|
|
117
|
-
for (const event of events) {
|
|
118
|
-
const message = String(event.errorMessage || '').toLowerCase();
|
|
119
|
-
if (message.includes('reference number mismatch')) {
|
|
120
|
-
mismatches.push(event);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (String(event.status || '').toLowerCase() === 'success') {
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return mismatches;
|
|
128
|
-
}
|
|
129
71
|
function resolveConnectedEnvironmentLabel(explicitEnvironment, candidates) {
|
|
130
72
|
const parsedExplicitEnvironment = parseTaskforceCloudEnvironment(explicitEnvironment);
|
|
131
73
|
if (parsedExplicitEnvironment) {
|
|
@@ -203,7 +145,7 @@ export function StandaloneLayout(props) {
|
|
|
203
145
|
// Form Actions
|
|
204
146
|
handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
|
|
205
147
|
// Other Props for Form
|
|
206
|
-
taxonomies, activeTypes, priorities, taxonomyDisplayLabels, approaches, copiedId, handleCopyId, handleToggleInProgress, handleToggleComplete, handleToggleReview, handleToggleCancel, handleArchiveTask, handleDelete, handleUnarchive, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, fetchTasks, searchQuery, setSearchQuery, filterCategories, setFilterCategories, filterTypes, setFilterTypes, filterPriorities, setFilterPriorities, filterAssignees, setFilterAssignees, assigneeOptions, taskScope, setTaskScope,
|
|
148
|
+
taxonomies, activeTypes, priorities, taxonomyDisplayLabels, approaches, copiedId, handleCopyId, handleToggleInProgress, handleToggleComplete, handleToggleReview, handleToggleCancel, handleArchiveTask, handleDelete, handleUnarchive, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, fetchTasks, searchQuery, setSearchQuery, filterCategories, setFilterCategories, filterTypes, setFilterTypes, filterPriorities, setFilterPriorities, filterAssignees, setFilterAssignees, hasInitedFilters, assigneeOptions, taskScope, setTaskScope,
|
|
207
149
|
// filterStatus, setFilterStatus, // This was duplicated, removed one instance
|
|
208
150
|
sortBy, setSortBy, clearFilters, // setSearchQuery, // This was duplicated, removed one instance
|
|
209
151
|
// filterCategories, setFilterCategories, // This was duplicated, removed one instance
|
|
@@ -214,7 +156,7 @@ export function StandaloneLayout(props) {
|
|
|
214
156
|
// clearFilters, // This was duplicated, removed one instance
|
|
215
157
|
// activeTypes, // This was duplicated, removed one instance
|
|
216
158
|
// Settings State & Props
|
|
217
|
-
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
159
|
+
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, fetchLoginMethods, unlinkLoginMethod, addPasswordToAccount, beginOAuthLogin, beginOAuthLink, availableAuthProviders, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
218
160
|
// Browser Props
|
|
219
161
|
showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
|
|
220
162
|
// Settings Managers
|
|
@@ -270,6 +212,9 @@ export function StandaloneLayout(props) {
|
|
|
270
212
|
const [showFilters, setShowFilters] = useState(false);
|
|
271
213
|
const [taskModalFullscreen, setTaskModalFullscreen] = useState(false);
|
|
272
214
|
const [planningDrawerOpen, setPlanningDrawerOpen] = useState(false);
|
|
215
|
+
const [documentTrayOpen, setDocumentTrayOpen] = useState(true);
|
|
216
|
+
const [imageTrayOpen, setImageTrayOpen] = useState(true);
|
|
217
|
+
const [agentTrayOpen, setAgentTrayOpen] = useState(true);
|
|
273
218
|
const [planningTreeCollapsed, setPlanningTreeCollapsed] = useState(false);
|
|
274
219
|
const [planningPrimaryCollapsed, setPlanningPrimaryCollapsed] = useState(false);
|
|
275
220
|
const [planningSecondaryCollapsed, setPlanningSecondaryCollapsed] = useState(false);
|
|
@@ -284,6 +229,13 @@ export function StandaloneLayout(props) {
|
|
|
284
229
|
const [planningDraftDescription, setPlanningDraftDescription] = useState('');
|
|
285
230
|
const [planningDraftOwner, setPlanningDraftOwner] = useState('');
|
|
286
231
|
const [planningDraftInitiativeId, setPlanningDraftInitiativeId] = useState('');
|
|
232
|
+
const [isSubmittingPlanningEditor, setIsSubmittingPlanningEditor] = useState(false);
|
|
233
|
+
const taskContextAttachmentDraftRef = useRef({
|
|
234
|
+
taskId: null,
|
|
235
|
+
attachments: [],
|
|
236
|
+
});
|
|
237
|
+
const planningContextAttachmentDraftsRef = useRef(new Map());
|
|
238
|
+
const planningContextAttachmentUpdateQueueRef = useRef(new Map());
|
|
287
239
|
const [documentTypeFilters, setDocumentTypeFilters] = useState(() => DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
|
|
288
240
|
const [documentAttachmentFilters, setDocumentAttachmentFilters] = useState(() => DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
|
|
289
241
|
const [requestedDocPath, setRequestedDocPath] = useState(null);
|
|
@@ -293,10 +245,32 @@ export function StandaloneLayout(props) {
|
|
|
293
245
|
const [requestedAnnotatedOpenVersion, setRequestedAnnotatedOpenVersion] = useState(0);
|
|
294
246
|
const [canonicalAnnotatedImageReferenceLabels, setCanonicalAnnotatedImageReferenceLabels] = useState({});
|
|
295
247
|
const [layoutStateReady, setLayoutStateReady] = useState(false);
|
|
248
|
+
const planningEditorSubmitInFlightRef = useRef(false);
|
|
296
249
|
const loadedLayoutStateWorkspaceRef = useRef('');
|
|
297
250
|
const requestedAnnotatedTargetRef = useRef(null);
|
|
298
251
|
const requestedAnnotatedSessionIdRef = useRef(null);
|
|
299
252
|
const annotatedImageReferenceFetchesRef = useRef(new Set());
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
taskContextAttachmentDraftRef.current = {
|
|
255
|
+
taskId: editingTaskId || null,
|
|
256
|
+
attachments,
|
|
257
|
+
};
|
|
258
|
+
}, [editingTaskId, attachments]);
|
|
259
|
+
const updateTaskContextAttachments = useCallback((getNextAttachments) => {
|
|
260
|
+
const taskId = editingTaskId || null;
|
|
261
|
+
const draft = taskContextAttachmentDraftRef.current;
|
|
262
|
+
const currentAttachments = draft.taskId === taskId ? draft.attachments : attachments;
|
|
263
|
+
const nextAttachments = getNextAttachments(currentAttachments);
|
|
264
|
+
taskContextAttachmentDraftRef.current = {
|
|
265
|
+
taskId,
|
|
266
|
+
attachments: nextAttachments,
|
|
267
|
+
};
|
|
268
|
+
setAttachments(nextAttachments);
|
|
269
|
+
setAttachmentsDirty(true);
|
|
270
|
+
if (taskId) {
|
|
271
|
+
void handleUpdateTask(taskId, { attachments: nextAttachments });
|
|
272
|
+
}
|
|
273
|
+
}, [attachments, editingTaskId, handleUpdateTask, setAttachments, setAttachmentsDirty]);
|
|
300
274
|
const workspaceUiStateHeaders = useMemo(() => {
|
|
301
275
|
const headers = {};
|
|
302
276
|
const workspaceIdHeader = String(currentWorkspaceId || '').trim();
|
|
@@ -307,7 +281,33 @@ export function StandaloneLayout(props) {
|
|
|
307
281
|
}, [runtimeMode, currentWorkspaceId]);
|
|
308
282
|
const layoutStateWorkspaceKey = useMemo(() => `${runtimeMode}:${String(currentWorkspaceId || 'default').trim() || 'default'}`, [runtimeMode, currentWorkspaceId]);
|
|
309
283
|
const annotatedLayoutCacheKey = useMemo(() => `taskforce:annotate-layout:${layoutStateWorkspaceKey}`, [layoutStateWorkspaceKey]);
|
|
284
|
+
const taskOriginWorkspaceModuleKey = useMemo(() => `taskforce:task-origin-workspace-module:${layoutStateWorkspaceKey}`, [layoutStateWorkspaceKey]);
|
|
310
285
|
const annotatedWorkspaceReady = useMemo(() => (runtimeMode === 'cloud' ? authSessionResolved : configLoaded), [authSessionResolved, configLoaded, runtimeMode]);
|
|
286
|
+
const openedTaskOriginWorkspaceModuleRef = useRef(false);
|
|
287
|
+
const taskOriginWorkspaceModuleRestoreHandledRef = useRef(false);
|
|
288
|
+
const rememberTaskOriginWorkspaceModule = useCallback((taskId) => {
|
|
289
|
+
const normalizedTaskId = String(taskId || '').trim();
|
|
290
|
+
if (!normalizedTaskId || typeof window === 'undefined')
|
|
291
|
+
return;
|
|
292
|
+
openedTaskOriginWorkspaceModuleRef.current = true;
|
|
293
|
+
try {
|
|
294
|
+
window.sessionStorage.setItem(taskOriginWorkspaceModuleKey, normalizedTaskId);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// ignore cache write failure
|
|
298
|
+
}
|
|
299
|
+
}, [taskOriginWorkspaceModuleKey]);
|
|
300
|
+
const clearTaskOriginWorkspaceModule = useCallback(() => {
|
|
301
|
+
openedTaskOriginWorkspaceModuleRef.current = false;
|
|
302
|
+
if (typeof window === 'undefined')
|
|
303
|
+
return;
|
|
304
|
+
try {
|
|
305
|
+
window.sessionStorage.removeItem(taskOriginWorkspaceModuleKey);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// ignore cache clear failure
|
|
309
|
+
}
|
|
310
|
+
}, [taskOriginWorkspaceModuleKey]);
|
|
311
311
|
useEffect(() => {
|
|
312
312
|
requestedAnnotatedTargetRef.current = requestedAnnotatedTarget;
|
|
313
313
|
}, [requestedAnnotatedTarget]);
|
|
@@ -354,6 +354,7 @@ export function StandaloneLayout(props) {
|
|
|
354
354
|
pushNotice(documentsWorkspaceUnavailableMessage, 'error');
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
|
+
rememberTaskOriginWorkspaceModule(customEvent.detail?.taskId);
|
|
357
358
|
setRequestedDocPath(fsPath || null);
|
|
358
359
|
setRequestedDocAssetId(assetId);
|
|
359
360
|
setActiveWorkspaceModule('docs');
|
|
@@ -362,7 +363,7 @@ export function StandaloneLayout(props) {
|
|
|
362
363
|
return () => {
|
|
363
364
|
window.removeEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
|
|
364
365
|
};
|
|
365
|
-
}, [documentsWorkspaceEnabled, documentsWorkspaceUnavailableMessage, pushNotice, setActiveWorkspaceModule]);
|
|
366
|
+
}, [documentsWorkspaceEnabled, documentsWorkspaceUnavailableMessage, pushNotice, rememberTaskOriginWorkspaceModule, setActiveWorkspaceModule]);
|
|
366
367
|
useEffect(() => {
|
|
367
368
|
if (resolvedWorkspaceModule !== activeWorkspaceModule) {
|
|
368
369
|
setActiveWorkspaceModule(resolvedWorkspaceModule);
|
|
@@ -393,6 +394,29 @@ export function StandaloneLayout(props) {
|
|
|
393
394
|
// ignore cache read failure
|
|
394
395
|
}
|
|
395
396
|
}, [annotatedLayoutCacheKey]);
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
if (taskOriginWorkspaceModuleRestoreHandledRef.current)
|
|
399
|
+
return;
|
|
400
|
+
if (resolvedWorkspaceModule !== 'docs' && resolvedWorkspaceModule !== 'annotate')
|
|
401
|
+
return;
|
|
402
|
+
taskOriginWorkspaceModuleRestoreHandledRef.current = true;
|
|
403
|
+
if (openedTaskOriginWorkspaceModuleRef.current || typeof window === 'undefined')
|
|
404
|
+
return;
|
|
405
|
+
try {
|
|
406
|
+
const taskId = String(window.sessionStorage.getItem(taskOriginWorkspaceModuleKey) || '').trim();
|
|
407
|
+
if (!taskId)
|
|
408
|
+
return;
|
|
409
|
+
window.sessionStorage.removeItem(taskOriginWorkspaceModuleKey);
|
|
410
|
+
setActiveWorkspaceModule('tasks');
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// ignore cache read failure
|
|
414
|
+
}
|
|
415
|
+
}, [resolvedWorkspaceModule, setActiveWorkspaceModule, taskOriginWorkspaceModuleKey]);
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
taskOriginWorkspaceModuleRestoreHandledRef.current = false;
|
|
418
|
+
openedTaskOriginWorkspaceModuleRef.current = false;
|
|
419
|
+
}, [taskOriginWorkspaceModuleKey]);
|
|
396
420
|
useEffect(() => {
|
|
397
421
|
if (loadedLayoutStateWorkspaceRef.current === layoutStateWorkspaceKey)
|
|
398
422
|
return;
|
|
@@ -442,6 +466,15 @@ export function StandaloneLayout(props) {
|
|
|
442
466
|
if (typeof state.planningDrawerOpen === 'boolean') {
|
|
443
467
|
setPlanningDrawerOpen(state.planningDrawerOpen);
|
|
444
468
|
}
|
|
469
|
+
if (typeof state.documentTrayOpen === 'boolean') {
|
|
470
|
+
setDocumentTrayOpen(state.documentTrayOpen);
|
|
471
|
+
}
|
|
472
|
+
if (typeof state.imageTrayOpen === 'boolean') {
|
|
473
|
+
setImageTrayOpen(state.imageTrayOpen);
|
|
474
|
+
}
|
|
475
|
+
if (typeof state.agentTrayOpen === 'boolean') {
|
|
476
|
+
setAgentTrayOpen(state.agentTrayOpen);
|
|
477
|
+
}
|
|
445
478
|
if (typeof state.planningTreeCollapsed === 'boolean') {
|
|
446
479
|
setPlanningTreeCollapsed(state.planningTreeCollapsed);
|
|
447
480
|
}
|
|
@@ -517,6 +550,9 @@ export function StandaloneLayout(props) {
|
|
|
517
550
|
showFilters,
|
|
518
551
|
documentTypeFilters,
|
|
519
552
|
documentAttachmentFilters,
|
|
553
|
+
documentTrayOpen,
|
|
554
|
+
imageTrayOpen,
|
|
555
|
+
agentTrayOpen,
|
|
520
556
|
planningDrawerOpen,
|
|
521
557
|
planningTreeCollapsed,
|
|
522
558
|
planningPrimaryCollapsed,
|
|
@@ -546,6 +582,9 @@ export function StandaloneLayout(props) {
|
|
|
546
582
|
showFilters,
|
|
547
583
|
documentTypeFilters,
|
|
548
584
|
documentAttachmentFilters,
|
|
585
|
+
documentTrayOpen,
|
|
586
|
+
imageTrayOpen,
|
|
587
|
+
agentTrayOpen,
|
|
549
588
|
planningDrawerOpen,
|
|
550
589
|
planningTreeCollapsed,
|
|
551
590
|
planningPrimaryCollapsed,
|
|
@@ -623,6 +662,7 @@ export function StandaloneLayout(props) {
|
|
|
623
662
|
setRequestedAnnotatedTarget(detail);
|
|
624
663
|
setRequestedAnnotatedSessionId(null);
|
|
625
664
|
setRequestedAnnotatedOpenVersion((current) => current + 1);
|
|
665
|
+
rememberTaskOriginWorkspaceModule(detail.taskId);
|
|
626
666
|
setActiveWorkspaceModule('annotate');
|
|
627
667
|
};
|
|
628
668
|
window.addEventListener('taskforce:open-annotated-attachment', handleOpenAnnotatedAttachment);
|
|
@@ -634,6 +674,7 @@ export function StandaloneLayout(props) {
|
|
|
634
674
|
annotatedAttachmentsWorkspaceEnabled,
|
|
635
675
|
annotatedAttachmentsWorkspaceUnavailableMessage,
|
|
636
676
|
pushNotice,
|
|
677
|
+
rememberTaskOriginWorkspaceModule,
|
|
637
678
|
setActiveWorkspaceModule
|
|
638
679
|
]);
|
|
639
680
|
useEffect(() => {
|
|
@@ -729,7 +770,14 @@ export function StandaloneLayout(props) {
|
|
|
729
770
|
}, [props.deletedTasks]);
|
|
730
771
|
const filteredDeletedTasks = useMemo(() => {
|
|
731
772
|
const deletedTasks = deletedScopeAllTasks;
|
|
732
|
-
const
|
|
773
|
+
const normalizedSearch = normalizeTaskSearchQuery(searchQuery);
|
|
774
|
+
const searchContext = {
|
|
775
|
+
categories: activeCategories,
|
|
776
|
+
types: activeTypes,
|
|
777
|
+
priorities,
|
|
778
|
+
assigneeOptions: props.assigneeOptions,
|
|
779
|
+
taxonomies
|
|
780
|
+
};
|
|
733
781
|
const allCategoriesSelected = activeCategories.every((taskCategory) => filterCategories.includes(taskCategory.value));
|
|
734
782
|
const allTypesSelected = activeTypes.every((taskType) => filterTypes.includes(taskType.value));
|
|
735
783
|
const selectedPriorities = new Set(filterPriorities.map((value) => String(value)));
|
|
@@ -737,12 +785,7 @@ export function StandaloneLayout(props) {
|
|
|
737
785
|
const selectedStatuses = new Set(filterStatus.map((value) => String(value)));
|
|
738
786
|
const allStatusesSelected = STATUS_OPTIONS.every((statusOption) => selectedStatuses.has(String(statusOption.value)));
|
|
739
787
|
return deletedTasks.filter((task) => {
|
|
740
|
-
const
|
|
741
|
-
const matchesSearch = !searchNeedle
|
|
742
|
-
|| task.title.toLowerCase().includes(searchNeedle)
|
|
743
|
-
|| String(task.description || '').toLowerCase().includes(searchNeedle)
|
|
744
|
-
|| task.id.toLowerCase().includes(searchNeedle)
|
|
745
|
-
|| referenceLabel.includes(searchNeedle);
|
|
788
|
+
const matchesSearch = taskMatchesSearch(task, normalizedSearch, searchContext);
|
|
746
789
|
const matchesCategory = allCategoriesSelected || filterCategories.includes(task.category);
|
|
747
790
|
const matchesType = allTypesSelected || filterTypes.includes(task.type || DEFAULT_TYPE_VALUE);
|
|
748
791
|
const matchesPriority = allPrioritiesSelected || selectedPriorities.has(String(task.priority));
|
|
@@ -836,94 +879,144 @@ export function StandaloneLayout(props) {
|
|
|
836
879
|
: taskScope === 'deleted'
|
|
837
880
|
? deletedScopeAllTasks
|
|
838
881
|
: props.tasks.filter((task) => !task.isArchived);
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
882
|
+
const planningPreviewTasks = taskScope === 'deleted'
|
|
883
|
+
? deletedScopeAllTasks
|
|
884
|
+
: Array.from([...props.tasks, ...archivedScopeAllTasks]
|
|
885
|
+
.reduce((map, task) => map.set(task.id, task), new Map())
|
|
886
|
+
.values());
|
|
887
|
+
const summarizeTaskCounts = (taskCount, completedTaskCount) => {
|
|
842
888
|
return {
|
|
843
889
|
progressPercent: taskCount > 0 ? Math.round((completedTaskCount / taskCount) * 100) : 0,
|
|
844
890
|
taskCount,
|
|
845
891
|
completedTaskCount,
|
|
846
892
|
};
|
|
847
893
|
};
|
|
894
|
+
const useBootstrapTaskSummaries = taskScope === 'open'
|
|
895
|
+
&& props.planningBootstrapTaskSummaries.length > 0
|
|
896
|
+
&& (props.loadingTasks || props.tasks.length === 0);
|
|
897
|
+
const workstreamTaskSummaryById = new Map();
|
|
898
|
+
const workstreamTaskPreviewsById = new Map();
|
|
899
|
+
if (useBootstrapTaskSummaries) {
|
|
900
|
+
props.planningBootstrapTaskSummaries.forEach((summary) => {
|
|
901
|
+
workstreamTaskSummaryById.set(summary.workstreamId, summary);
|
|
902
|
+
workstreamTaskPreviewsById.set(summary.workstreamId, summary.tasks || []);
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
planningScopedTasks.forEach((task) => {
|
|
907
|
+
const workstreamId = String(task.workstreamId || '').trim();
|
|
908
|
+
if (!workstreamId)
|
|
909
|
+
return;
|
|
910
|
+
const current = workstreamTaskSummaryById.get(workstreamId) || {
|
|
911
|
+
workstreamId,
|
|
912
|
+
taskCount: 0,
|
|
913
|
+
completedTaskCount: 0,
|
|
914
|
+
tasks: [],
|
|
915
|
+
};
|
|
916
|
+
current.taskCount += 1;
|
|
917
|
+
if (task.isArchived || task.status === 'done' || task.status === 'cancelled') {
|
|
918
|
+
current.completedTaskCount += 1;
|
|
919
|
+
}
|
|
920
|
+
workstreamTaskSummaryById.set(workstreamId, current);
|
|
921
|
+
});
|
|
922
|
+
planningPreviewTasks.forEach((task) => {
|
|
923
|
+
const workstreamId = String(task.workstreamId || '').trim();
|
|
924
|
+
if (!workstreamId)
|
|
925
|
+
return;
|
|
926
|
+
const current = workstreamTaskPreviewsById.get(workstreamId) || [];
|
|
927
|
+
current.push({
|
|
928
|
+
id: task.id,
|
|
929
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
930
|
+
title: task.title,
|
|
931
|
+
status: task.status || null,
|
|
932
|
+
isArchived: Boolean(task.isArchived),
|
|
933
|
+
});
|
|
934
|
+
workstreamTaskPreviewsById.set(workstreamId, current);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
848
937
|
const workstreamTaskIds = new Map();
|
|
849
938
|
const workstreamInitiativeIds = new Map();
|
|
850
939
|
const initiativeTaskIds = new Map();
|
|
851
|
-
const
|
|
852
|
-
const initiativeById = new Map();
|
|
940
|
+
const workstreamsByInitiativeId = new Map();
|
|
853
941
|
const buildWorkstreamSummary = (workstream) => {
|
|
854
|
-
const
|
|
855
|
-
const taskPreviews =
|
|
856
|
-
id: task.id,
|
|
857
|
-
referenceNumber: task.referenceNumber ?? null,
|
|
858
|
-
title: task.title,
|
|
859
|
-
status: task.status || null,
|
|
860
|
-
}));
|
|
942
|
+
const taskSummary = workstreamTaskSummaryById.get(workstream.id);
|
|
943
|
+
const taskPreviews = workstreamTaskPreviewsById.get(workstream.id) || taskSummary?.tasks || [];
|
|
861
944
|
const summary = {
|
|
862
945
|
id: workstream.id,
|
|
863
946
|
referenceNumber: workstream.referenceNumber ?? null,
|
|
864
947
|
title: workstream.title,
|
|
865
948
|
description: String(workstream.description || '').trim() || undefined,
|
|
949
|
+
ownerId: workstream.ownerId ? String(workstream.ownerId) : null,
|
|
866
950
|
ownerLabel: workstream.ownerId ? assigneeLabelByValue.get(String(workstream.ownerId)) || String(workstream.ownerId) : null,
|
|
867
|
-
...
|
|
951
|
+
...summarizeTaskCounts(taskSummary?.taskCount || 0, taskSummary?.completedTaskCount || 0),
|
|
868
952
|
initiativeId: workstream.initiativeId || null,
|
|
869
953
|
commentCount: Array.isArray(workstream.comments) ? workstream.comments.length : 0,
|
|
870
954
|
attachmentCount: Array.isArray(workstream.attachments) ? workstream.attachments.length : 0,
|
|
955
|
+
attachments: Array.isArray(workstream.attachments) ? workstream.attachments : [],
|
|
871
956
|
isArchived: Boolean(workstream.isArchived),
|
|
872
957
|
tasks: taskPreviews,
|
|
873
958
|
};
|
|
874
|
-
workstreamTaskIds.set(workstream.id,
|
|
875
|
-
if (workstream.initiativeId)
|
|
959
|
+
workstreamTaskIds.set(workstream.id, taskPreviews.map((task) => task.id));
|
|
960
|
+
if (workstream.initiativeId) {
|
|
876
961
|
workstreamInitiativeIds.set(workstream.id, workstream.initiativeId);
|
|
877
|
-
|
|
962
|
+
const current = workstreamsByInitiativeId.get(workstream.initiativeId) || [];
|
|
963
|
+
current.push(summary);
|
|
964
|
+
workstreamsByInitiativeId.set(workstream.initiativeId, current);
|
|
965
|
+
}
|
|
878
966
|
return summary;
|
|
879
967
|
};
|
|
880
968
|
const allWorkstreamSummaries = props.workstreams.map(buildWorkstreamSummary);
|
|
969
|
+
const scopedWorkstreamSummaries = allWorkstreamSummaries.filter((workstream) => (isPlanningEntityVisibleForScope(taskScope, Boolean(workstream.isArchived))));
|
|
881
970
|
const allInitiatives = props.initiatives.map((initiative) => {
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
.filter((task) => Boolean(task));
|
|
971
|
+
const allWorkstreamsForInitiative = workstreamsByInitiativeId.get(initiative.id) || [];
|
|
972
|
+
const workstreams = allWorkstreamsForInitiative
|
|
973
|
+
.filter((workstream) => isPlanningEntityVisibleForScope(taskScope, Boolean(workstream.isArchived)));
|
|
974
|
+
const taskCount = workstreams.reduce((sum, workstream) => sum + workstream.taskCount, 0);
|
|
975
|
+
const completedTaskCount = workstreams.reduce((sum, workstream) => sum + workstream.completedTaskCount, 0);
|
|
888
976
|
const initiativeSummary = {
|
|
889
977
|
id: initiative.id,
|
|
890
978
|
referenceNumber: initiative.referenceNumber ?? null,
|
|
891
979
|
title: initiative.title,
|
|
892
980
|
description: String(initiative.description || '').trim() || undefined,
|
|
981
|
+
ownerId: initiative.ownerId ? String(initiative.ownerId) : null,
|
|
893
982
|
ownerLabel: initiative.ownerId ? assigneeLabelByValue.get(String(initiative.ownerId)) || String(initiative.ownerId) : null,
|
|
894
|
-
...
|
|
983
|
+
...summarizeTaskCounts(taskCount, completedTaskCount),
|
|
895
984
|
workstreamCount: workstreams.length,
|
|
896
985
|
workstreams,
|
|
986
|
+
allWorkstreams: allWorkstreamsForInitiative,
|
|
897
987
|
commentCount: Array.isArray(initiative.comments) ? initiative.comments.length : 0,
|
|
898
988
|
attachmentCount: Array.isArray(initiative.attachments) ? initiative.attachments.length : 0,
|
|
989
|
+
attachments: Array.isArray(initiative.attachments) ? initiative.attachments : [],
|
|
899
990
|
isArchived: Boolean(initiative.isArchived),
|
|
900
991
|
};
|
|
901
|
-
initiativeTaskIds.set(initiative.id, initiativeScopedTasks.map((task) => task.id));
|
|
902
|
-
initiativeById.set(initiativeSummary.id, initiativeSummary);
|
|
903
992
|
return initiativeSummary;
|
|
904
993
|
});
|
|
905
|
-
const initiatives =
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const standaloneWorkstreams = allWorkstreamSummaries
|
|
911
|
-
.filter((workstream) => !workstream.initiativeId)
|
|
912
|
-
.filter((workstream) => taskScope === 'deleted'
|
|
913
|
-
? false
|
|
914
|
-
: taskScope === 'archived'
|
|
915
|
-
? Boolean(workstream.isArchived)
|
|
916
|
-
: !workstream.isArchived);
|
|
994
|
+
const { initiatives, standaloneWorkstreams, visibleWorkstreams, initiativeById, workstreamById, } = scopePlanningSummaries(allInitiatives, scopedWorkstreamSummaries, taskScope);
|
|
995
|
+
const allStandaloneWorkstreams = allWorkstreamSummaries.filter((workstream) => !workstream.initiativeId);
|
|
996
|
+
initiatives.forEach((initiative) => {
|
|
997
|
+
initiativeTaskIds.set(initiative.id, initiative.workstreams.flatMap((workstream) => (workstream.tasks || []).map((task) => task.id)));
|
|
998
|
+
});
|
|
917
999
|
return {
|
|
918
1000
|
initiatives,
|
|
919
1001
|
standaloneWorkstreams,
|
|
1002
|
+
allStandaloneWorkstreams,
|
|
920
1003
|
workstreamTaskIds,
|
|
921
1004
|
workstreamInitiativeIds,
|
|
922
1005
|
initiativeTaskIds,
|
|
923
1006
|
workstreamById,
|
|
924
1007
|
initiativeById,
|
|
925
1008
|
};
|
|
926
|
-
}, [
|
|
1009
|
+
}, [
|
|
1010
|
+
archivedScopeAllTasks,
|
|
1011
|
+
assigneeLabelByValue,
|
|
1012
|
+
deletedScopeAllTasks,
|
|
1013
|
+
props.initiatives,
|
|
1014
|
+
props.loadingTasks,
|
|
1015
|
+
props.planningBootstrapTaskSummaries,
|
|
1016
|
+
props.tasks,
|
|
1017
|
+
props.workstreams,
|
|
1018
|
+
taskScope,
|
|
1019
|
+
]);
|
|
927
1020
|
const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
|
|
928
1021
|
const referenceLabel = formatInitiativeReference(initiative);
|
|
929
1022
|
return {
|
|
@@ -1198,17 +1291,7 @@ export function StandaloneLayout(props) {
|
|
|
1198
1291
|
const [showAccountMenu, setShowAccountMenu] = useState(false);
|
|
1199
1292
|
const [showAccountHub, setShowAccountHub] = useState(false);
|
|
1200
1293
|
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
|
1201
|
-
const [
|
|
1202
|
-
const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
|
|
1203
|
-
const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
|
|
1204
|
-
const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
|
|
1205
|
-
const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
|
|
1206
|
-
const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
|
|
1207
|
-
const [syncRecentEvents, setSyncRecentEvents] = useState([]);
|
|
1208
|
-
const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
|
|
1209
|
-
const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
|
|
1210
|
-
const syncEventsListRef = useRef(null);
|
|
1211
|
-
const pendingSyncEventsScrollRestoreRef = useRef(null);
|
|
1294
|
+
const [showAvatarPhotoManager, setShowAvatarPhotoManager] = useState(false);
|
|
1212
1295
|
const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
|
|
1213
1296
|
const [workspaceActionError, setWorkspaceActionError] = useState('');
|
|
1214
1297
|
const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
|
|
@@ -1249,7 +1332,6 @@ export function StandaloneLayout(props) {
|
|
|
1249
1332
|
const [profileSaveNotice, setProfileSaveNotice] = useState(null);
|
|
1250
1333
|
const TEAM_AUDIT_PAGE_SIZE = 25;
|
|
1251
1334
|
const accountMenuRef = useRef(null);
|
|
1252
|
-
const profileAvatarInputRef = useRef(null);
|
|
1253
1335
|
const accountSurfaceOpenStartedAtRef = useRef(null);
|
|
1254
1336
|
const accountSurfacePendingRef = useRef({
|
|
1255
1337
|
billing: false,
|
|
@@ -1274,21 +1356,11 @@ export function StandaloneLayout(props) {
|
|
|
1274
1356
|
const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
|
|
1275
1357
|
const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
|
|
1276
1358
|
const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
|
|
1277
|
-
const handleWorkspaceSyncToggle
|
|
1278
|
-
if (!canManageWorkspaceSync)
|
|
1279
|
-
return;
|
|
1280
|
-
setWorkspaceSyncError(null);
|
|
1281
|
-
setWorkspaceSyncToggleBusy(true);
|
|
1282
|
-
const result = await saveWorkspaceCloudSyncSettings({ enabled });
|
|
1283
|
-
if (!result.success) {
|
|
1284
|
-
setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
|
|
1285
|
-
}
|
|
1286
|
-
setWorkspaceSyncToggleBusy(false);
|
|
1287
|
-
}, [
|
|
1359
|
+
const { showSyncStatusModal, showSyncEnableWarning, workspaceSyncError, syncControlBusy, openSyncStatusModal, closeSyncStatusModal, handleWorkspaceSyncToggle, cancelSyncEnableWarning, confirmSyncEnableWarning, } = useSyncStatusControls({
|
|
1288
1360
|
canManageWorkspaceSync,
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1361
|
+
workspaceCloudSyncEnabled,
|
|
1362
|
+
saveWorkspaceCloudSyncSettings,
|
|
1363
|
+
});
|
|
1292
1364
|
const currentWorkspaceLabel = useMemo(() => {
|
|
1293
1365
|
const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
|
|
1294
1366
|
const namedWorkspace = String(current?.name || '').trim();
|
|
@@ -1348,23 +1420,21 @@ export function StandaloneLayout(props) {
|
|
|
1348
1420
|
if (!normalizedDraftId || !cloudAuthConfigured)
|
|
1349
1421
|
return;
|
|
1350
1422
|
try {
|
|
1351
|
-
|
|
1352
|
-
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1353
|
-
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1354
|
-
await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/discard', {
|
|
1423
|
+
await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/discard'), {
|
|
1355
1424
|
method: 'POST',
|
|
1356
1425
|
headers: { 'Content-Type': 'application/json' },
|
|
1357
1426
|
credentials: 'include',
|
|
1358
1427
|
body: JSON.stringify({ draftId: normalizedDraftId })
|
|
1359
|
-
}
|
|
1428
|
+
});
|
|
1360
1429
|
}
|
|
1361
1430
|
catch {
|
|
1362
1431
|
// Best-effort cleanup only.
|
|
1363
1432
|
}
|
|
1364
|
-
}, [cloudAuthConfigured,
|
|
1433
|
+
}, [cloudAuthConfigured, resolveCloudAuthUrl]);
|
|
1365
1434
|
const closeEditProfileModal = useCallback(() => {
|
|
1366
1435
|
const pendingDraftId = profileAvatarDraftId;
|
|
1367
1436
|
setShowEditProfileModal(false);
|
|
1437
|
+
setShowAvatarPhotoManager(false);
|
|
1368
1438
|
setProfileSaveError(null);
|
|
1369
1439
|
setProfileSaveNotice(null);
|
|
1370
1440
|
setProfileAvatarDraftId(null);
|
|
@@ -1375,47 +1445,53 @@ export function StandaloneLayout(props) {
|
|
|
1375
1445
|
}, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
|
|
1376
1446
|
const handleProfileAvatarSelected = useCallback(async (file) => {
|
|
1377
1447
|
if (!file || !cloudAuthConfigured)
|
|
1378
|
-
return;
|
|
1448
|
+
return false;
|
|
1379
1449
|
if (!file.type.startsWith('image/')) {
|
|
1380
1450
|
setProfileSaveError('Profile photo must be an image file.');
|
|
1381
1451
|
setProfileSaveNotice(null);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
if (file.size > PROFILE_AVATAR_MAX_BYTES) {
|
|
1385
|
-
setProfileSaveError('Profile photo must be 5 MB or smaller.');
|
|
1386
|
-
setProfileSaveNotice(null);
|
|
1387
|
-
return;
|
|
1452
|
+
return false;
|
|
1388
1453
|
}
|
|
1389
1454
|
setProfileAvatarBusy(true);
|
|
1390
1455
|
setProfileSaveError(null);
|
|
1391
1456
|
setProfileSaveNotice(null);
|
|
1392
1457
|
try {
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1458
|
+
const preparedAvatar = await prepareAvatarUploadFile(file, {
|
|
1459
|
+
maxBytes: PROFILE_AVATAR_MAX_BYTES
|
|
1460
|
+
});
|
|
1461
|
+
if (preparedAvatar.exceededLimit) {
|
|
1462
|
+
const message = file.type === 'image/gif'
|
|
1463
|
+
? 'Animated GIF profile photos must be 5 MB or smaller.'
|
|
1464
|
+
: 'Profile photo must be 5 MB or smaller.';
|
|
1465
|
+
throw new Error(message);
|
|
1466
|
+
}
|
|
1467
|
+
const uploadFile = preparedAvatar.file;
|
|
1468
|
+
const initRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/init'), {
|
|
1397
1469
|
method: 'POST',
|
|
1398
1470
|
headers: { 'Content-Type': 'application/json' },
|
|
1399
1471
|
credentials: 'include',
|
|
1400
1472
|
body: JSON.stringify({
|
|
1401
|
-
originalName:
|
|
1402
|
-
mimeType:
|
|
1403
|
-
size:
|
|
1473
|
+
originalName: uploadFile.name,
|
|
1474
|
+
mimeType: uploadFile.type,
|
|
1475
|
+
size: uploadFile.size
|
|
1404
1476
|
})
|
|
1405
|
-
}
|
|
1477
|
+
});
|
|
1406
1478
|
const initData = await initRes.json().catch(() => ({}));
|
|
1407
|
-
if (!initRes.ok || !initData?.success || typeof initData?.
|
|
1479
|
+
if (!initRes.ok || !initData?.success || typeof initData?.draftId !== 'string' || typeof initData?.relativePath !== 'string') {
|
|
1408
1480
|
throw new Error(initData?.error || 'Failed to start avatar upload.');
|
|
1409
1481
|
}
|
|
1410
|
-
const uploadRes = await fetch(
|
|
1411
|
-
method:
|
|
1412
|
-
headers:
|
|
1413
|
-
|
|
1482
|
+
const uploadRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/upload'), {
|
|
1483
|
+
method: 'POST',
|
|
1484
|
+
headers: {
|
|
1485
|
+
'Content-Type': uploadFile.type || 'application/octet-stream',
|
|
1486
|
+
'x-taskforce-avatar-draft-id': initData.draftId
|
|
1487
|
+
},
|
|
1488
|
+
credentials: 'include',
|
|
1489
|
+
body: uploadFile
|
|
1414
1490
|
});
|
|
1415
1491
|
if (!uploadRes.ok) {
|
|
1416
1492
|
throw new Error('Failed to upload avatar.');
|
|
1417
1493
|
}
|
|
1418
|
-
const finalizeRes = await
|
|
1494
|
+
const finalizeRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/finalize'), {
|
|
1419
1495
|
method: 'POST',
|
|
1420
1496
|
headers: { 'Content-Type': 'application/json' },
|
|
1421
1497
|
credentials: 'include',
|
|
@@ -1423,7 +1499,7 @@ export function StandaloneLayout(props) {
|
|
|
1423
1499
|
draftId: initData.draftId,
|
|
1424
1500
|
relativePath: initData.relativePath
|
|
1425
1501
|
})
|
|
1426
|
-
}
|
|
1502
|
+
});
|
|
1427
1503
|
const finalizeData = await finalizeRes.json().catch(() => ({}));
|
|
1428
1504
|
if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
|
|
1429
1505
|
throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
|
|
@@ -1431,27 +1507,24 @@ export function StandaloneLayout(props) {
|
|
|
1431
1507
|
const previousDraftId = profileAvatarDraftId;
|
|
1432
1508
|
setProfileAvatarDraftId(finalizeData.draftId);
|
|
1433
1509
|
setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
|
|
1434
|
-
setProfileSaveNotice(
|
|
1510
|
+
setProfileSaveNotice(null);
|
|
1435
1511
|
if (previousDraftId && previousDraftId !== finalizeData.draftId) {
|
|
1436
1512
|
void discardProfileAvatarDraft(previousDraftId);
|
|
1437
1513
|
}
|
|
1514
|
+
return true;
|
|
1438
1515
|
}
|
|
1439
1516
|
catch (error) {
|
|
1440
1517
|
setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
|
|
1518
|
+
return false;
|
|
1441
1519
|
}
|
|
1442
1520
|
finally {
|
|
1443
1521
|
setProfileAvatarBusy(false);
|
|
1444
|
-
if (profileAvatarInputRef.current) {
|
|
1445
|
-
profileAvatarInputRef.current.value = '';
|
|
1446
|
-
}
|
|
1447
1522
|
}
|
|
1448
1523
|
}, [
|
|
1449
1524
|
cloudAuthConfigured,
|
|
1450
1525
|
discardProfileAvatarDraft,
|
|
1451
1526
|
profileAvatarDraftId,
|
|
1452
|
-
|
|
1453
|
-
props.config?.cloudAuthBaseUrl,
|
|
1454
|
-
runtimeMode
|
|
1527
|
+
resolveCloudAuthUrl
|
|
1455
1528
|
]);
|
|
1456
1529
|
const handleDiscardPendingProfileAvatar = useCallback(() => {
|
|
1457
1530
|
if (!profileAvatarDraftId)
|
|
@@ -1497,6 +1570,7 @@ export function StandaloneLayout(props) {
|
|
|
1497
1570
|
setProfileAvatarDraftId(null);
|
|
1498
1571
|
setProfileAvatarPreviewUrl('');
|
|
1499
1572
|
setProfileSaveBusy(false);
|
|
1573
|
+
setShowAvatarPhotoManager(false);
|
|
1500
1574
|
setShowEditProfileModal(false);
|
|
1501
1575
|
}, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
|
|
1502
1576
|
const teamAdminHeaders = useCallback(() => {
|
|
@@ -1815,9 +1889,9 @@ export function StandaloneLayout(props) {
|
|
|
1815
1889
|
const target = (!targetRaw || targetRaw === '/login' || !targetRaw.startsWith('/')) ? '/' : targetRaw;
|
|
1816
1890
|
setShowAccountMenu(false);
|
|
1817
1891
|
setShowAccountHub(false);
|
|
1818
|
-
|
|
1892
|
+
closeSyncStatusModal();
|
|
1819
1893
|
navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
|
|
1820
|
-
}, [location.hash, location.pathname, location.search, navigate]);
|
|
1894
|
+
}, [closeSyncStatusModal, location.hash, location.pathname, location.search, navigate]);
|
|
1821
1895
|
const openWorkspaceSetup = useCallback((options) => {
|
|
1822
1896
|
setShowAccountMenu(false);
|
|
1823
1897
|
setShowCreateWorkspaceConfirm(false);
|
|
@@ -1830,43 +1904,83 @@ export function StandaloneLayout(props) {
|
|
|
1830
1904
|
const handlePlansBack = useCallback(async () => {
|
|
1831
1905
|
if (plansNavigationBusy)
|
|
1832
1906
|
return;
|
|
1907
|
+
setPlansNavigationBusy(true);
|
|
1833
1908
|
if (!isAuthenticated) {
|
|
1834
|
-
|
|
1909
|
+
try {
|
|
1910
|
+
closePlansScreen();
|
|
1911
|
+
}
|
|
1912
|
+
finally {
|
|
1913
|
+
setPlansNavigationBusy(false);
|
|
1914
|
+
}
|
|
1835
1915
|
return;
|
|
1836
1916
|
}
|
|
1837
|
-
const
|
|
1838
|
-
if (!resolveCommercialLifecyclePresentation(
|
|
1917
|
+
const currentSummary = accountProfileSummaryRef.current;
|
|
1918
|
+
if (currentSummary && !resolveCommercialLifecyclePresentation(currentSummary).allowReturnToApp) {
|
|
1919
|
+
setPlansNavigationBusy(false);
|
|
1839
1920
|
return;
|
|
1840
1921
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1922
|
+
closePlansScreen();
|
|
1923
|
+
void (async () => {
|
|
1924
|
+
try {
|
|
1925
|
+
const result = await props.continueAfterCommercialOnboarding();
|
|
1926
|
+
if (!result.success) {
|
|
1927
|
+
pushNotice(result.error || 'Unable to finish onboarding.', 'error');
|
|
1928
|
+
if (result.destination === 'login') {
|
|
1929
|
+
navigate('/login', { replace: true });
|
|
1930
|
+
}
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
if (result.destination === 'setup') {
|
|
1934
|
+
openWorkspaceSetup();
|
|
1848
1935
|
}
|
|
1849
|
-
return;
|
|
1850
1936
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
return;
|
|
1937
|
+
finally {
|
|
1938
|
+
setPlansNavigationBusy(false);
|
|
1854
1939
|
}
|
|
1855
|
-
|
|
1856
|
-
}
|
|
1857
|
-
finally {
|
|
1858
|
-
setPlansNavigationBusy(false);
|
|
1859
|
-
}
|
|
1940
|
+
})();
|
|
1860
1941
|
}, [
|
|
1942
|
+
accountProfileSummaryRef,
|
|
1861
1943
|
closePlansScreen,
|
|
1862
1944
|
isAuthenticated,
|
|
1863
|
-
loadAccountProfileSummary,
|
|
1864
1945
|
navigate,
|
|
1865
1946
|
openWorkspaceSetup,
|
|
1866
1947
|
plansNavigationBusy,
|
|
1867
1948
|
props,
|
|
1868
1949
|
pushNotice
|
|
1869
1950
|
]);
|
|
1951
|
+
const handleOpenKanbanFromBrand = useCallback(() => {
|
|
1952
|
+
if (isPlansScreen) {
|
|
1953
|
+
void handlePlansBack();
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
handleNavigation(() => {
|
|
1957
|
+
clearTaskOriginWorkspaceModule();
|
|
1958
|
+
setActiveWorkspaceModule('tasks');
|
|
1959
|
+
setPlanningDrawerOpen(false);
|
|
1960
|
+
setPlanningDrawerDetail(null);
|
|
1961
|
+
setPlanningNestedWorkstreamDetailId(null);
|
|
1962
|
+
setPlanningEditor(null);
|
|
1963
|
+
setTaskModalFullscreen(false);
|
|
1964
|
+
if (activeTab === 'add')
|
|
1965
|
+
resetForm();
|
|
1966
|
+
setActiveTab('tasks');
|
|
1967
|
+
if (location.pathname !== '/' || location.search) {
|
|
1968
|
+
navigate('/');
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
}, [
|
|
1972
|
+
activeTab,
|
|
1973
|
+
clearTaskOriginWorkspaceModule,
|
|
1974
|
+
handleNavigation,
|
|
1975
|
+
handlePlansBack,
|
|
1976
|
+
isPlansScreen,
|
|
1977
|
+
location.pathname,
|
|
1978
|
+
location.search,
|
|
1979
|
+
navigate,
|
|
1980
|
+
resetForm,
|
|
1981
|
+
setActiveTab,
|
|
1982
|
+
setActiveWorkspaceModule,
|
|
1983
|
+
]);
|
|
1870
1984
|
const handleSwitchWorkspace = useCallback(async (workspaceId) => {
|
|
1871
1985
|
if (!workspaceId || workspaceActionBusy)
|
|
1872
1986
|
return;
|
|
@@ -2046,6 +2160,7 @@ export function StandaloneLayout(props) {
|
|
|
2046
2160
|
const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
|
|
2047
2161
|
const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
|
|
2048
2162
|
const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
|
|
2163
|
+
const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
|
|
2049
2164
|
const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
|
|
2050
2165
|
runtimeMode,
|
|
2051
2166
|
isAuthenticated,
|
|
@@ -2053,7 +2168,10 @@ export function StandaloneLayout(props) {
|
|
|
2053
2168
|
workspaceCloudSyncEnabled,
|
|
2054
2169
|
workspaceSyncSummary,
|
|
2055
2170
|
workspaceSyncRecommendedAction,
|
|
2056
|
-
workspaceSyncError: rawSyncLastError
|
|
2171
|
+
workspaceSyncError: rawSyncLastError,
|
|
2172
|
+
pushBlockedReason: workspaceSyncDiagnostics.pushBlockedReason || null,
|
|
2173
|
+
pullBlockedReason: workspaceSyncDiagnostics.pullBlockedReason || null,
|
|
2174
|
+
retryBlockedReason: workspaceSyncDiagnostics.retryBlockedReason || null
|
|
2057
2175
|
}), [
|
|
2058
2176
|
runtimeMode,
|
|
2059
2177
|
isAuthenticated,
|
|
@@ -2061,75 +2179,24 @@ export function StandaloneLayout(props) {
|
|
|
2061
2179
|
workspaceCloudSyncEnabled,
|
|
2062
2180
|
workspaceSyncSummary,
|
|
2063
2181
|
workspaceSyncRecommendedAction,
|
|
2064
|
-
rawSyncLastError
|
|
2182
|
+
rawSyncLastError,
|
|
2183
|
+
workspaceSyncDiagnostics.pushBlockedReason,
|
|
2184
|
+
workspaceSyncDiagnostics.pullBlockedReason,
|
|
2185
|
+
workspaceSyncDiagnostics.retryBlockedReason
|
|
2065
2186
|
]);
|
|
2066
2187
|
const syncLastError = headerSyncPresentation.lastError;
|
|
2067
2188
|
const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
|
|
2068
|
-
const
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
return 'Initial cloud pull';
|
|
2080
|
-
case 'active':
|
|
2081
|
-
return 'Active';
|
|
2082
|
-
case 'error':
|
|
2083
|
-
return 'Error';
|
|
2084
|
-
default:
|
|
2085
|
-
return 'Idle';
|
|
2086
|
-
}
|
|
2087
|
-
}, [workspaceSyncPhase, workspaceSyncRepairBusy]);
|
|
2088
|
-
const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
|
|
2089
|
-
const syncDiagnosticsSummary = useMemo(() => ([
|
|
2090
|
-
{
|
|
2091
|
-
label: 'AI profile snapshot',
|
|
2092
|
-
value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
|
|
2093
|
-
},
|
|
2094
|
-
{
|
|
2095
|
-
label: 'AI profile raw response',
|
|
2096
|
-
value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
|
|
2097
|
-
},
|
|
2098
|
-
{
|
|
2099
|
-
label: 'Last pushed AI profiles',
|
|
2100
|
-
value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
|
|
2101
|
-
},
|
|
2102
|
-
{
|
|
2103
|
-
label: 'AI watermark map',
|
|
2104
|
-
value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
|
|
2105
|
-
},
|
|
2106
|
-
{
|
|
2107
|
-
label: 'Document snapshot',
|
|
2108
|
-
value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
|
|
2109
|
-
},
|
|
2110
|
-
{
|
|
2111
|
-
label: 'Asset snapshot',
|
|
2112
|
-
value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
|
|
2113
|
-
},
|
|
2114
|
-
{
|
|
2115
|
-
label: 'Queued full AI sync',
|
|
2116
|
-
value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
|
|
2117
|
-
},
|
|
2118
|
-
{
|
|
2119
|
-
label: 'AI snapshot last fetch',
|
|
2120
|
-
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
|
|
2121
|
-
? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
|
|
2122
|
-
: 'Never'
|
|
2123
|
-
},
|
|
2124
|
-
{
|
|
2125
|
-
label: 'AI snapshot fetch error',
|
|
2126
|
-
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
|
|
2127
|
-
},
|
|
2128
|
-
{
|
|
2129
|
-
label: 'AI snapshot skip reason',
|
|
2130
|
-
value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
|
|
2131
|
-
}
|
|
2132
|
-
]), [workspaceSyncDiagnostics]);
|
|
2189
|
+
const syncDiagnosticsSummary = useMemo(() => buildSyncDiagnosticsSummary(workspaceSyncDiagnostics), [workspaceSyncDiagnostics]);
|
|
2190
|
+
const { syncRecentEvents, syncRecentEventsLoading, syncRecentEventsError, syncEventsListRef, loadRecentSyncEvents, } = useRecentSyncEvents({
|
|
2191
|
+
isOpen: showSyncStatusModal,
|
|
2192
|
+
workspaceId: currentWorkspaceId,
|
|
2193
|
+
refreshKeys: [
|
|
2194
|
+
workspaceLastPushAt,
|
|
2195
|
+
workspaceLastPullAt,
|
|
2196
|
+
workspaceLastErrorAt,
|
|
2197
|
+
workspaceSyncStatus
|
|
2198
|
+
]
|
|
2199
|
+
});
|
|
2133
2200
|
const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
|
|
2134
2201
|
&& workspaceSyncStatus === 'syncing'
|
|
2135
2202
|
&& workspaceSyncPhase === 'active'
|
|
@@ -2144,282 +2211,34 @@ export function StandaloneLayout(props) {
|
|
|
2144
2211
|
}, 1200);
|
|
2145
2212
|
return () => window.clearTimeout(timeoutId);
|
|
2146
2213
|
}, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
|
|
2147
|
-
const
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
? event.eventType.trim()
|
|
2153
|
-
: 'sync';
|
|
2154
|
-
const status = event.status === 'error' ? 'failed' : 'succeeded';
|
|
2155
|
-
const changeCountValue = Number(event.changeCount);
|
|
2156
|
-
const changeCount = Number.isFinite(changeCountValue)
|
|
2157
|
-
? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
|
|
2158
|
-
: '';
|
|
2159
|
-
const requestMsValue = Number(event.requestMs);
|
|
2160
|
-
const requestMs = Number.isFinite(requestMsValue)
|
|
2161
|
-
? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
|
|
2162
|
-
: '';
|
|
2163
|
-
const statusCodeValue = Number(event.statusCode);
|
|
2164
|
-
const statusCode = Number.isFinite(statusCodeValue)
|
|
2165
|
-
? ` [${Math.floor(statusCodeValue)}]`
|
|
2166
|
-
: '';
|
|
2167
|
-
const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
|
|
2168
|
-
? `: ${event.errorMessage.trim()}`
|
|
2169
|
-
: '';
|
|
2170
|
-
return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
|
|
2171
|
-
}, []);
|
|
2172
|
-
const syncEventRows = useMemo(() => {
|
|
2173
|
-
if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
|
|
2174
|
-
return [{
|
|
2175
|
-
key: 'loading',
|
|
2176
|
-
text: 'Loading recent sync events...',
|
|
2177
|
-
tone: 'muted'
|
|
2178
|
-
}];
|
|
2179
|
-
}
|
|
2180
|
-
if (syncRecentEventsError) {
|
|
2181
|
-
return [{
|
|
2182
|
-
key: 'error',
|
|
2183
|
-
text: syncRecentEventsError,
|
|
2184
|
-
tone: 'error'
|
|
2185
|
-
}];
|
|
2186
|
-
}
|
|
2187
|
-
if (syncRecentEvents.length === 0) {
|
|
2188
|
-
return [{
|
|
2189
|
-
key: 'empty',
|
|
2190
|
-
text: 'No recent sync events recorded.',
|
|
2191
|
-
tone: 'muted'
|
|
2192
|
-
}];
|
|
2193
|
-
}
|
|
2194
|
-
return syncRecentEvents.map((event, index) => ({
|
|
2195
|
-
key: getSyncEventStableKey(event, index),
|
|
2196
|
-
text: formatSyncEventLine(event),
|
|
2197
|
-
tone: event.status === 'error' ? 'error' : 'default'
|
|
2198
|
-
}));
|
|
2199
|
-
}, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
|
|
2214
|
+
const syncEventRows = useMemo(() => buildSyncEventRows({
|
|
2215
|
+
syncRecentEvents,
|
|
2216
|
+
syncRecentEventsError,
|
|
2217
|
+
syncRecentEventsLoading
|
|
2218
|
+
}), [syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
|
|
2200
2219
|
const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
|
|
2201
|
-
const
|
|
2202
|
-
const
|
|
2203
|
-
|
|
2204
|
-
const pathValue = String(details?.path || '').trim();
|
|
2205
|
-
const referenceLabelValue = String(details?.referenceLabel || '').trim();
|
|
2206
|
-
const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
|
|
2207
|
-
const existingRefValue = Number(details?.existingReferenceNumber);
|
|
2208
|
-
const incomingRefValue = Number(details?.incomingReferenceNumber);
|
|
2209
|
-
const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
|
|
2210
|
-
const refsLabel = (Number.isFinite(existingRefValue)
|
|
2211
|
-
|| Number.isFinite(incomingRefValue))
|
|
2212
|
-
? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
|
|
2213
|
-
: referenceLabelValue
|
|
2214
|
-
? `Both claimed ${referenceLabelValue}`
|
|
2215
|
-
: null;
|
|
2216
|
-
return {
|
|
2217
|
-
key: getSyncEventStableKey(event, index),
|
|
2218
|
-
pathLabel,
|
|
2219
|
-
refsLabel,
|
|
2220
|
-
};
|
|
2221
|
-
})), [activeReferenceMismatchEvents]);
|
|
2222
|
-
useLayoutEffect(() => {
|
|
2223
|
-
const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
|
|
2224
|
-
const listEl = syncEventsListRef.current;
|
|
2225
|
-
if (!pendingRestore || !listEl)
|
|
2226
|
-
return;
|
|
2227
|
-
const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
|
|
2228
|
-
listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
|
|
2229
|
-
pendingSyncEventsScrollRestoreRef.current = null;
|
|
2230
|
-
}, [syncRecentEvents]);
|
|
2231
|
-
const loadRecentSyncEvents = useCallback(async () => {
|
|
2232
|
-
const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
|
|
2233
|
-
method: 'GET',
|
|
2234
|
-
credentials: 'include'
|
|
2235
|
-
});
|
|
2236
|
-
if (!res.ok) {
|
|
2237
|
-
throw new Error(`Failed to load sync events (${res.status})`);
|
|
2238
|
-
}
|
|
2239
|
-
const data = await res.json().catch(() => ({}));
|
|
2240
|
-
return Array.isArray(data?.events)
|
|
2241
|
-
? data.events.filter((event) => event && typeof event === 'object')
|
|
2242
|
-
: [];
|
|
2243
|
-
}, [currentWorkspaceId]);
|
|
2244
|
-
useEffect(() => {
|
|
2245
|
-
if (!showSyncStatusModal)
|
|
2246
|
-
return;
|
|
2247
|
-
let cancelled = false;
|
|
2248
|
-
const isInitialLoad = syncRecentEvents.length === 0;
|
|
2249
|
-
if (isInitialLoad) {
|
|
2250
|
-
setSyncRecentEventsLoading(true);
|
|
2251
|
-
}
|
|
2252
|
-
void loadRecentSyncEvents()
|
|
2253
|
-
.then((events) => {
|
|
2254
|
-
if (cancelled)
|
|
2255
|
-
return;
|
|
2256
|
-
const listEl = syncEventsListRef.current;
|
|
2257
|
-
pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
|
|
2258
|
-
? {
|
|
2259
|
-
scrollTop: listEl.scrollTop,
|
|
2260
|
-
scrollHeight: listEl.scrollHeight
|
|
2261
|
-
}
|
|
2262
|
-
: null;
|
|
2263
|
-
setSyncRecentEvents((current) => {
|
|
2264
|
-
const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
|
|
2265
|
-
return areSyncEventListsEqual(current, next) ? current : next;
|
|
2266
|
-
});
|
|
2267
|
-
setSyncRecentEventsError(null);
|
|
2268
|
-
})
|
|
2269
|
-
.catch((error) => {
|
|
2270
|
-
if (cancelled)
|
|
2271
|
-
return;
|
|
2272
|
-
const message = error instanceof Error && error.message.trim().length > 0
|
|
2273
|
-
? error.message.trim()
|
|
2274
|
-
: 'Unable to load recent sync events.';
|
|
2275
|
-
setSyncRecentEventsError(message);
|
|
2276
|
-
if (isInitialLoad) {
|
|
2277
|
-
setSyncRecentEvents([]);
|
|
2278
|
-
}
|
|
2279
|
-
})
|
|
2280
|
-
.finally(() => {
|
|
2281
|
-
if (cancelled)
|
|
2282
|
-
return;
|
|
2283
|
-
if (isInitialLoad) {
|
|
2284
|
-
setSyncRecentEventsLoading(false);
|
|
2285
|
-
}
|
|
2286
|
-
});
|
|
2287
|
-
return () => {
|
|
2288
|
-
cancelled = true;
|
|
2289
|
-
};
|
|
2290
|
-
}, [
|
|
2291
|
-
showSyncStatusModal,
|
|
2292
|
-
loadRecentSyncEvents,
|
|
2293
|
-
syncRecentEvents.length,
|
|
2294
|
-
workspaceLastPushAt,
|
|
2295
|
-
workspaceLastPullAt,
|
|
2296
|
-
workspaceLastErrorAt,
|
|
2297
|
-
workspaceSyncStatus
|
|
2298
|
-
]);
|
|
2299
|
-
const syncStatusMeta = useMemo(() => {
|
|
2300
|
-
switch (headerSyncStatus) {
|
|
2301
|
-
case 'off':
|
|
2302
|
-
return {
|
|
2303
|
-
label: 'Off',
|
|
2304
|
-
icon: 'off',
|
|
2305
|
-
color: '#94a3b8',
|
|
2306
|
-
border: 'rgba(148, 163, 184, 0.35)',
|
|
2307
|
-
background: 'rgba(148, 163, 184, 0.12)'
|
|
2308
|
-
};
|
|
2309
|
-
case 'syncing':
|
|
2310
|
-
return {
|
|
2311
|
-
label: 'Syncing',
|
|
2312
|
-
icon: 'cloud',
|
|
2313
|
-
color: '#60a5fa',
|
|
2314
|
-
border: 'rgba(96, 165, 250, 0.35)',
|
|
2315
|
-
background: 'rgba(59, 130, 246, 0.12)'
|
|
2316
|
-
};
|
|
2317
|
-
case 'attention':
|
|
2318
|
-
return {
|
|
2319
|
-
label: 'Needs Attention',
|
|
2320
|
-
icon: 'cloud',
|
|
2321
|
-
color: '#fda4af',
|
|
2322
|
-
border: 'rgba(253, 164, 175, 0.4)',
|
|
2323
|
-
background: 'rgba(239, 68, 68, 0.12)'
|
|
2324
|
-
};
|
|
2325
|
-
default:
|
|
2326
|
-
return {
|
|
2327
|
-
label: 'Healthy',
|
|
2328
|
-
icon: 'cloud',
|
|
2329
|
-
color: '#86efac',
|
|
2330
|
-
border: 'rgba(134, 239, 172, 0.4)',
|
|
2331
|
-
background: 'rgba(34, 197, 94, 0.12)'
|
|
2332
|
-
};
|
|
2333
|
-
}
|
|
2334
|
-
}, [headerSyncStatus]);
|
|
2335
|
-
const handleCopySyncDetails = useCallback(async () => {
|
|
2336
|
-
const details = [
|
|
2337
|
-
'Taskforce Sync Manager',
|
|
2338
|
-
`Workspace ID: ${currentWorkspaceId}`,
|
|
2339
|
-
`Status: ${syncStatusMeta.label}`,
|
|
2340
|
-
`Summary: ${headerSyncPresentation.summary}`,
|
|
2341
|
-
`Last successful sync: ${formattedLastSyncTime}`,
|
|
2342
|
-
`Last pull from cloud: ${formattedLastPullTime}`,
|
|
2343
|
-
`Last push to cloud: ${formattedLastPushTime}`,
|
|
2344
|
-
`Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
|
|
2345
|
-
`Pending local changes: ${workspaceSyncPendingChanges}`,
|
|
2346
|
-
`Last error: ${syncLastError}`,
|
|
2347
|
-
`Sync stage: ${syncStageLabel}`,
|
|
2348
|
-
`AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
|
|
2349
|
-
`AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
|
|
2350
|
-
`Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
|
|
2351
|
-
`AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
|
|
2352
|
-
`AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
|
|
2353
|
-
`AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
|
|
2354
|
-
`AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
|
|
2355
|
-
`Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
|
|
2356
|
-
`Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
|
|
2357
|
-
`Reference mismatches detected: ${referenceMismatchCount}`,
|
|
2358
|
-
`Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
|
|
2359
|
-
];
|
|
2360
|
-
try {
|
|
2361
|
-
const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
|
|
2362
|
-
const recentEventLines = events.map((event) => formatSyncEventLine(event));
|
|
2363
|
-
const copyText = [
|
|
2364
|
-
...details,
|
|
2365
|
-
'',
|
|
2366
|
-
'Recent sync events',
|
|
2367
|
-
...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
|
|
2368
|
-
].join('\n');
|
|
2369
|
-
await navigator.clipboard.writeText(copyText);
|
|
2370
|
-
setWorkspaceSyncCopied(true);
|
|
2371
|
-
window.setTimeout(() => {
|
|
2372
|
-
setWorkspaceSyncCopied(false);
|
|
2373
|
-
}, 1800);
|
|
2374
|
-
pushNotice('Sync details copied to clipboard.', 'success');
|
|
2375
|
-
}
|
|
2376
|
-
catch {
|
|
2377
|
-
setWorkspaceSyncCopied(false);
|
|
2378
|
-
pushNotice('Failed to copy sync details.', 'error');
|
|
2379
|
-
}
|
|
2380
|
-
}, [
|
|
2220
|
+
const activeReferenceMismatchSummaries = useMemo(() => buildReferenceMismatchSummaries(syncRecentEvents), [syncRecentEvents]);
|
|
2221
|
+
const syncStatusMeta = useMemo(() => resolveSyncStatusMeta(headerSyncStatus), [headerSyncStatus]);
|
|
2222
|
+
const { workspaceSyncRepairBusy, workspaceSyncRepairQueued, workspaceSyncCopied, handleCopySyncDetails, handleQueueOrRunRepairSync, } = useSyncStatusActions({
|
|
2381
2223
|
currentWorkspaceId,
|
|
2382
|
-
syncStatusMeta.label,
|
|
2383
|
-
headerSyncPresentation.summary,
|
|
2224
|
+
syncStatusLabel: syncStatusMeta.label,
|
|
2225
|
+
workspaceSyncSummary: headerSyncPresentation.summary,
|
|
2226
|
+
workspaceSyncRecommendedAction: headerSyncPresentation.recommendedAction,
|
|
2384
2227
|
formattedLastSyncTime,
|
|
2385
2228
|
formattedLastPullTime,
|
|
2386
2229
|
formattedLastPushTime,
|
|
2387
2230
|
workspaceSyncDiagnostics,
|
|
2388
2231
|
workspaceSyncPendingChanges,
|
|
2389
2232
|
syncLastError,
|
|
2390
|
-
|
|
2233
|
+
workspaceSyncPhase,
|
|
2391
2234
|
referenceMismatchCount,
|
|
2392
|
-
pushNotice,
|
|
2393
2235
|
syncRecentEvents,
|
|
2394
2236
|
loadRecentSyncEvents,
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
try {
|
|
2401
|
-
await resetWorkspaceSyncCursorAndPull();
|
|
2402
|
-
}
|
|
2403
|
-
finally {
|
|
2404
|
-
setWorkspaceSyncRepairBusy(false);
|
|
2405
|
-
}
|
|
2406
|
-
}, [resetWorkspaceSyncCursorAndPull]);
|
|
2407
|
-
const handleQueueOrRunRepairSync = useCallback(() => {
|
|
2408
|
-
if (workspaceSyncRepairBusy)
|
|
2409
|
-
return;
|
|
2410
|
-
if (workspaceSyncBusy) {
|
|
2411
|
-
setWorkspaceSyncRepairQueued(true);
|
|
2412
|
-
return;
|
|
2413
|
-
}
|
|
2414
|
-
void handleRepairSync();
|
|
2415
|
-
}, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
|
|
2416
|
-
useEffect(() => {
|
|
2417
|
-
if (!workspaceSyncRepairQueued)
|
|
2418
|
-
return;
|
|
2419
|
-
if (workspaceSyncBusy || workspaceSyncRepairBusy)
|
|
2420
|
-
return;
|
|
2421
|
-
void handleRepairSync();
|
|
2422
|
-
}, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
|
|
2237
|
+
pushNotice,
|
|
2238
|
+
resetWorkspaceSyncCursorAndPull,
|
|
2239
|
+
workspaceSyncBusy,
|
|
2240
|
+
});
|
|
2241
|
+
const syncStageLabel = useMemo(() => resolveSyncStageLabel(workspaceSyncPhase, workspaceSyncRepairBusy, workspaceSyncBusy), [workspaceSyncBusy, workspaceSyncPhase, workspaceSyncRepairBusy]);
|
|
2423
2242
|
const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
|
|
2424
2243
|
const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
|
|
2425
2244
|
const scheduleDates = useMemo(() => {
|
|
@@ -2640,6 +2459,8 @@ export function StandaloneLayout(props) {
|
|
|
2640
2459
|
setActiveTab('add');
|
|
2641
2460
|
}, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
|
|
2642
2461
|
const openPlanningEditor = useCallback((editor) => {
|
|
2462
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2463
|
+
setIsSubmittingPlanningEditor(false);
|
|
2643
2464
|
setPlanningEditor(editor);
|
|
2644
2465
|
setPlanningPrimaryCollapsed(false);
|
|
2645
2466
|
setPlanningSecondaryEditor(null);
|
|
@@ -2654,6 +2475,8 @@ export function StandaloneLayout(props) {
|
|
|
2654
2475
|
setPlanningNestedWorkstreamDetailId(null);
|
|
2655
2476
|
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2656
2477
|
const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
|
|
2478
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2479
|
+
setIsSubmittingPlanningEditor(false);
|
|
2657
2480
|
setPlanningSecondaryEditor(editor);
|
|
2658
2481
|
setPlanningSecondaryCollapsed(false);
|
|
2659
2482
|
setPlanningDraftTitle('');
|
|
@@ -2667,6 +2490,8 @@ export function StandaloneLayout(props) {
|
|
|
2667
2490
|
setPlanningNestedWorkstreamDetailId(null);
|
|
2668
2491
|
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2669
2492
|
const closePlanningEditor = useCallback(() => {
|
|
2493
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2494
|
+
setIsSubmittingPlanningEditor(false);
|
|
2670
2495
|
setPlanningEditor(null);
|
|
2671
2496
|
setPlanningDraftTitle('');
|
|
2672
2497
|
setPlanningDraftDescription('');
|
|
@@ -2674,6 +2499,8 @@ export function StandaloneLayout(props) {
|
|
|
2674
2499
|
setPlanningDraftInitiativeId('');
|
|
2675
2500
|
}, []);
|
|
2676
2501
|
const closePlanningSecondaryEditor = useCallback(() => {
|
|
2502
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2503
|
+
setIsSubmittingPlanningEditor(false);
|
|
2677
2504
|
setPlanningSecondaryEditor(null);
|
|
2678
2505
|
setPlanningDraftTitle('');
|
|
2679
2506
|
setPlanningDraftDescription('');
|
|
@@ -2682,8 +2509,10 @@ export function StandaloneLayout(props) {
|
|
|
2682
2509
|
}, []);
|
|
2683
2510
|
const submitPlanningEditor = useCallback(async () => {
|
|
2684
2511
|
const activeEditor = planningSecondaryEditor || planningEditor;
|
|
2685
|
-
if (!activeEditor)
|
|
2512
|
+
if (!activeEditor || planningEditorSubmitInFlightRef.current)
|
|
2686
2513
|
return;
|
|
2514
|
+
planningEditorSubmitInFlightRef.current = true;
|
|
2515
|
+
setIsSubmittingPlanningEditor(true);
|
|
2687
2516
|
const isSecondaryEditor = Boolean(planningSecondaryEditor);
|
|
2688
2517
|
const ownerId = planningDraftOwner.trim() || null;
|
|
2689
2518
|
try {
|
|
@@ -2757,6 +2586,10 @@ export function StandaloneLayout(props) {
|
|
|
2757
2586
|
catch (error) {
|
|
2758
2587
|
pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
|
|
2759
2588
|
}
|
|
2589
|
+
finally {
|
|
2590
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2591
|
+
setIsSubmittingPlanningEditor(false);
|
|
2592
|
+
}
|
|
2760
2593
|
}, [
|
|
2761
2594
|
closePlanningEditor,
|
|
2762
2595
|
closePlanningSecondaryEditor,
|
|
@@ -2814,13 +2647,6 @@ export function StandaloneLayout(props) {
|
|
|
2814
2647
|
setPlanningSecondaryCollapsed(false);
|
|
2815
2648
|
setPlanningNestedWorkstreamDetailId(workstreamId);
|
|
2816
2649
|
}, []);
|
|
2817
|
-
const ownerValueByLabel = useMemo(() => {
|
|
2818
|
-
const map = new Map();
|
|
2819
|
-
assigneeOptions.forEach((option) => {
|
|
2820
|
-
map.set(option.label, String(option.value));
|
|
2821
|
-
});
|
|
2822
|
-
return map;
|
|
2823
|
-
}, [assigneeOptions]);
|
|
2824
2650
|
const openPlanningCreateEditor = useCallback((type, initiativeId) => {
|
|
2825
2651
|
if (type === 'workstream' && initiativeId) {
|
|
2826
2652
|
openPlanningSecondaryEditor({ mode: 'create', entityType: 'workstream' }, initiativeId);
|
|
@@ -2842,7 +2668,7 @@ export function StandaloneLayout(props) {
|
|
|
2842
2668
|
setPlanningEditor({ mode: 'edit', entityType: 'initiative', targetId: id });
|
|
2843
2669
|
setPlanningDraftTitle(item.title);
|
|
2844
2670
|
setPlanningDraftDescription(item.description || '');
|
|
2845
|
-
setPlanningDraftOwner(item.
|
|
2671
|
+
setPlanningDraftOwner(item.ownerId ? String(item.ownerId) : '');
|
|
2846
2672
|
setPlanningDraftInitiativeId('');
|
|
2847
2673
|
}
|
|
2848
2674
|
else {
|
|
@@ -2853,12 +2679,12 @@ export function StandaloneLayout(props) {
|
|
|
2853
2679
|
setPlanningEditor({ mode: 'edit', entityType: 'workstream', targetId: id });
|
|
2854
2680
|
setPlanningDraftTitle(item.title);
|
|
2855
2681
|
setPlanningDraftDescription(item.description || '');
|
|
2856
|
-
setPlanningDraftOwner(item.
|
|
2682
|
+
setPlanningDraftOwner(item.ownerId ? String(item.ownerId) : '');
|
|
2857
2683
|
const initiative = item.initiativeId ? planningStructure.initiativeById.get(item.initiativeId) || null : null;
|
|
2858
2684
|
setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
|
|
2859
2685
|
}
|
|
2860
2686
|
setPlanningNestedWorkstreamDetailId(null);
|
|
2861
|
-
}, [
|
|
2687
|
+
}, [planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
|
|
2862
2688
|
const handleAssignInitiativeToWorkstream = useCallback(async (workstreamId, initiativeReferenceOverride) => {
|
|
2863
2689
|
const workstream = props.workstreams.find((item) => item.id === workstreamId);
|
|
2864
2690
|
if (!workstream) {
|
|
@@ -2966,6 +2792,54 @@ export function StandaloneLayout(props) {
|
|
|
2966
2792
|
}
|
|
2967
2793
|
await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
|
|
2968
2794
|
}, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
|
|
2795
|
+
const getPlanningContextAttachmentDraft = useCallback((entityType, entityId) => {
|
|
2796
|
+
const key = `${entityType}:${entityId}`;
|
|
2797
|
+
const draft = planningContextAttachmentDraftsRef.current.get(key);
|
|
2798
|
+
if (draft)
|
|
2799
|
+
return draft;
|
|
2800
|
+
const entity = entityType === 'initiative'
|
|
2801
|
+
? props.initiatives.find((initiative) => initiative.id === entityId)
|
|
2802
|
+
: props.workstreams.find((workstream) => workstream.id === entityId);
|
|
2803
|
+
return Array.isArray(entity?.attachments) ? entity.attachments : [];
|
|
2804
|
+
}, [props.initiatives, props.workstreams]);
|
|
2805
|
+
const updatePlanningContextAttachments = useCallback((entityType, entityId, nextAttachments) => {
|
|
2806
|
+
const key = `${entityType}:${entityId}`;
|
|
2807
|
+
planningContextAttachmentDraftsRef.current.set(key, nextAttachments);
|
|
2808
|
+
const previousUpdate = planningContextAttachmentUpdateQueueRef.current.get(key) || Promise.resolve();
|
|
2809
|
+
const nextUpdate = previousUpdate
|
|
2810
|
+
.catch(() => undefined)
|
|
2811
|
+
.then(() => (entityType === 'initiative'
|
|
2812
|
+
? props.updateInitiative(entityId, { attachments: nextAttachments })
|
|
2813
|
+
: props.updateWorkstream(entityId, { attachments: nextAttachments })));
|
|
2814
|
+
planningContextAttachmentUpdateQueueRef.current.set(key, nextUpdate);
|
|
2815
|
+
void nextUpdate.catch((error) => {
|
|
2816
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to update planning context.', 'error');
|
|
2817
|
+
}).finally(() => {
|
|
2818
|
+
if (planningContextAttachmentUpdateQueueRef.current.get(key) === nextUpdate) {
|
|
2819
|
+
planningContextAttachmentUpdateQueueRef.current.delete(key);
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
}, [props, pushNotice]);
|
|
2823
|
+
const handleAddPlanningContextFile = useCallback((entityType, entityId, file) => {
|
|
2824
|
+
const current = getPlanningContextAttachmentDraft(entityType, entityId);
|
|
2825
|
+
updatePlanningContextAttachments(entityType, entityId, [...current, file]);
|
|
2826
|
+
}, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
|
|
2827
|
+
const handleRemovePlanningContextFile = useCallback((entityType, entityId, index) => {
|
|
2828
|
+
const current = getPlanningContextAttachmentDraft(entityType, entityId);
|
|
2829
|
+
updatePlanningContextAttachments(entityType, entityId, current.filter((_, i) => i !== index));
|
|
2830
|
+
}, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
|
|
2831
|
+
const handleUpdatePlanningContextCaption = useCallback((entityType, entityId, index, caption) => {
|
|
2832
|
+
const current = getPlanningContextAttachmentDraft(entityType, entityId);
|
|
2833
|
+
const nextAttachments = current.map((attachment, i) => {
|
|
2834
|
+
if (i !== index)
|
|
2835
|
+
return attachment;
|
|
2836
|
+
if (typeof attachment === 'string') {
|
|
2837
|
+
return { path: attachment, caption, timestamp: new Date().toISOString() };
|
|
2838
|
+
}
|
|
2839
|
+
return { ...attachment, caption };
|
|
2840
|
+
});
|
|
2841
|
+
updatePlanningContextAttachments(entityType, entityId, nextAttachments);
|
|
2842
|
+
}, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
|
|
2969
2843
|
const handleInitiativeFilterChange = useCallback((initiativeId) => {
|
|
2970
2844
|
setSelectedPlanningInitiativeId(initiativeId);
|
|
2971
2845
|
setSelectedPlanningWorkstreamId('');
|
|
@@ -2981,8 +2855,10 @@ export function StandaloneLayout(props) {
|
|
|
2981
2855
|
pushNotice(`${unavailableLabel} is not available in this build yet.`, 'error');
|
|
2982
2856
|
return;
|
|
2983
2857
|
}
|
|
2858
|
+
clearTaskOriginWorkspaceModule();
|
|
2984
2859
|
setActiveWorkspaceModule(nextModule);
|
|
2985
2860
|
}, [
|
|
2861
|
+
clearTaskOriginWorkspaceModule,
|
|
2986
2862
|
pushNotice,
|
|
2987
2863
|
setActiveWorkspaceModule,
|
|
2988
2864
|
workspaceModules,
|
|
@@ -3002,10 +2878,40 @@ export function StandaloneLayout(props) {
|
|
|
3002
2878
|
}
|
|
3003
2879
|
setPlanningDrawerOpen((current) => !current);
|
|
3004
2880
|
}, [planningDrawerOpen, resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
2881
|
+
const handleDocumentTrayToggle = useCallback(() => {
|
|
2882
|
+
if (resolvedWorkspaceModule !== 'docs') {
|
|
2883
|
+
setActiveWorkspaceModule('docs');
|
|
2884
|
+
setDocumentTrayOpen(true);
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
setDocumentTrayOpen((current) => !current);
|
|
2888
|
+
}, [resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
2889
|
+
const handleImageTrayToggle = useCallback(() => {
|
|
2890
|
+
if (resolvedWorkspaceModule !== 'annotate') {
|
|
2891
|
+
setActiveWorkspaceModule('annotate');
|
|
2892
|
+
setImageTrayOpen(true);
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
setImageTrayOpen((current) => !current);
|
|
2896
|
+
}, [resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
2897
|
+
const handleAgentTrayToggle = useCallback(() => {
|
|
2898
|
+
if (resolvedWorkspaceModule !== 'agents') {
|
|
2899
|
+
setActiveWorkspaceModule('agents');
|
|
2900
|
+
setAgentTrayOpen(true);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
setAgentTrayOpen((current) => !current);
|
|
2904
|
+
}, [resolvedWorkspaceModule, setActiveWorkspaceModule]);
|
|
3005
2905
|
const handleBackToTaskFromAnnotatedAttachment = useCallback((taskId) => {
|
|
2906
|
+
clearTaskOriginWorkspaceModule();
|
|
3006
2907
|
setActiveWorkspaceModule('tasks');
|
|
3007
2908
|
handleOpenTaskById(taskId);
|
|
3008
|
-
}, [handleOpenTaskById, setActiveWorkspaceModule]);
|
|
2909
|
+
}, [clearTaskOriginWorkspaceModule, handleOpenTaskById, setActiveWorkspaceModule]);
|
|
2910
|
+
const handleBackToTaskFromDocument = useCallback((taskId) => {
|
|
2911
|
+
clearTaskOriginWorkspaceModule();
|
|
2912
|
+
setActiveWorkspaceModule('tasks');
|
|
2913
|
+
handleOpenTaskById(taskId);
|
|
2914
|
+
}, [clearTaskOriginWorkspaceModule, handleOpenTaskById, setActiveWorkspaceModule]);
|
|
3009
2915
|
const handleAnnotatedAttachmentOpenTarget = useCallback((target, options) => {
|
|
3010
2916
|
setRequestedAnnotatedTarget(target);
|
|
3011
2917
|
setRequestedAnnotatedSessionId(options?.sessionId ?? null);
|
|
@@ -3196,16 +3102,45 @@ export function StandaloneLayout(props) {
|
|
|
3196
3102
|
workflows: _jsx(Workflow, { size: 18 }),
|
|
3197
3103
|
agents: _jsx(Bot, { size: 18 }),
|
|
3198
3104
|
};
|
|
3199
|
-
|
|
3105
|
+
const trayButtonDisabledClass = shellStyles.workspaceToolButtonDisabled || '';
|
|
3106
|
+
const workspaceTrayButton = (() => {
|
|
3107
|
+
if (resolvedWorkspaceModule === 'docs') {
|
|
3108
|
+
return (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${documentTrayOpen ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: handleDocumentTrayToggle, title: documentTrayOpen ? 'Collapse document tray' : 'Document tray', "aria-label": documentTrayOpen ? 'Collapse document tray' : 'Document tray', children: documentTrayOpen ? _jsx(ChevronLeft, { size: 18 }) : _jsx(Folder, { size: 18 }) }));
|
|
3109
|
+
}
|
|
3110
|
+
if (resolvedWorkspaceModule === 'annotate') {
|
|
3111
|
+
return (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${imageTrayOpen ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: handleImageTrayToggle, title: imageTrayOpen ? 'Collapse image tray' : 'Image tray', "aria-label": imageTrayOpen ? 'Collapse image tray' : 'Image tray', children: imageTrayOpen ? _jsx(ChevronLeft, { size: 18 }) : _jsx(Folder, { size: 18 }) }));
|
|
3112
|
+
}
|
|
3113
|
+
if (resolvedWorkspaceModule === 'agents') {
|
|
3114
|
+
return (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${agentTrayOpen ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: handleAgentTrayToggle, title: agentTrayOpen ? 'Collapse agent tray' : 'MCP settings tray', "aria-label": agentTrayOpen ? 'Collapse agent tray' : 'MCP settings tray', children: agentTrayOpen ? _jsx(ChevronLeft, { size: 18 }) : _jsx(Plug, { size: 18 }) }));
|
|
3115
|
+
}
|
|
3116
|
+
if (resolvedWorkspaceModule === 'workflows') {
|
|
3117
|
+
return (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${trayButtonDisabledClass}`.trim(), title: "Workflow tray coming soon", "aria-label": "Workflow tray coming soon", disabled: true, children: _jsx(Workflow, { size: 18 }) }));
|
|
3118
|
+
}
|
|
3119
|
+
return (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${(resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: handlePlanningToolToggle, title: resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? 'Collapse planning trays' : 'Planning', "aria-label": resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? 'Collapse planning trays' : 'Planning', children: resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? _jsx(ChevronLeft, { size: 18 }) : _jsx(NotebookTabs, { size: 18 }) }));
|
|
3120
|
+
})();
|
|
3121
|
+
return (_jsxs("aside", { className: shellStyles.workspaceToolRail, "aria-label": "Workspace tools", children: [_jsxs("div", { className: shellStyles.workspaceToolRailMain, children: [workspaceTrayButton, _jsx("div", { className: shellStyles.workspaceToolRailDivider }), workspaceToolModules.map((module) => (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${resolvedWorkspaceModule === module.id ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: () => handleModuleChange(module.id), title: module.label, "aria-label": module.label, children: moduleIcons[module.id] }, module.id)))] }), _jsxs("div", { className: shellStyles.workspaceToolRailBottom, children: [_jsx("div", { className: shellStyles.workspaceToolRailDivider }), _jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${activeTab === 'settings' ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: () => {
|
|
3200
3122
|
setSettingsSection('general');
|
|
3201
3123
|
setActiveTab('settings');
|
|
3202
3124
|
}, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
|
|
3203
|
-
}, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
|
|
3204
|
-
const
|
|
3205
|
-
const
|
|
3125
|
+
}, [activeTab, agentTrayOpen, documentTrayOpen, handleAgentTrayToggle, handleDocumentTrayToggle, handleImageTrayToggle, handleModuleChange, handlePlanningToolToggle, imageTrayOpen, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
|
|
3126
|
+
const [planningDrawerPortalElement, setPlanningDrawerPortalElement] = useState(null);
|
|
3127
|
+
const setPlanningDrawerPortalRef = useCallback((node) => {
|
|
3128
|
+
setPlanningDrawerPortalElement(node);
|
|
3129
|
+
}, []);
|
|
3130
|
+
const planningDrawerNode = (_jsx(PlanningDrawer, { open: planningDrawerOpen, leftOffset: WORKSPACE_TOOL_RAIL_WIDTH, initiatives: planningStructure.initiatives, standaloneWorkstreams: planningStructure.standaloneWorkstreams, allStandaloneWorkstreams: planningStructure.allStandaloneWorkstreams, activeInitiativeId: selectedPlanningInitiativeId, activeWorkstreamId: selectedPlanningWorkstreamId, expandedInitiativeIds: expandedPlanningInitiativeIds, detail: planningDrawerItem, editor: planningEditor, secondaryPane: planningSecondaryPane, currentWorkspaceId: currentWorkspaceId, assigneeOptions: assigneeOptions.map((option) => ({
|
|
3131
|
+
value: String(option.value),
|
|
3132
|
+
label: option.label,
|
|
3133
|
+
kind: option.kind,
|
|
3134
|
+
username: option.username ?? null,
|
|
3135
|
+
archivedAt: option.archivedAt ?? null
|
|
3136
|
+
})), draftInitiativeSummary: planningDraftInitiativeSummary, draftTitle: planningDraftTitle, draftDescription: planningDraftDescription, draftOwner: planningDraftOwner, draftInitiativeId: planningDraftInitiativeId, onChangeDraftTitle: setPlanningDraftTitle, onChangeDraftDescription: setPlanningDraftDescription, onChangeDraftOwner: setPlanningDraftOwner, onChangeDraftInitiativeId: setPlanningDraftInitiativeId, onCollapseTreePane: () => setPlanningTreeCollapsed(true), onExpandTreePane: () => setPlanningTreeCollapsed(false), onCollapsePrimaryPane: () => setPlanningPrimaryCollapsed(true), onExpandPrimaryPane: () => setPlanningPrimaryCollapsed(false), onCollapseSecondaryPane: () => setPlanningSecondaryCollapsed(true), onExpandSecondaryPane: () => setPlanningSecondaryCollapsed(false), onBackFromSecondary: () => {
|
|
3137
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
3138
|
+
setIsSubmittingPlanningEditor(false);
|
|
3206
3139
|
setPlanningSecondaryEditor(null);
|
|
3207
3140
|
setPlanningNestedWorkstreamDetailId(null);
|
|
3208
|
-
}, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onSubmitEditor: submitPlanningEditor, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: handleOpenTaskById,
|
|
3141
|
+
}, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onSubmitEditor: submitPlanningEditor, isSubmittingEditor: isSubmittingPlanningEditor, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: handleOpenTaskById, onRemoveTaskFromWorkstream: (taskId) => {
|
|
3142
|
+
void handleAssignTaskToWorkstream(taskId, null);
|
|
3143
|
+
}, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile: handleAddPlanningContextFile, onRemovePlanningContextFile: handleRemovePlanningContextFile, onUpdatePlanningContextCaption: handleUpdatePlanningContextCaption, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onArchiveInitiative: (initiativeId) => {
|
|
3209
3144
|
void props.archiveInitiative(initiativeId).then(() => {
|
|
3210
3145
|
pushNotice('Initiative archived', 'success');
|
|
3211
3146
|
}).catch((error) => {
|
|
@@ -3240,7 +3175,7 @@ export function StandaloneLayout(props) {
|
|
|
3240
3175
|
const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
|
|
3241
3176
|
? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
|
|
3242
3177
|
: null;
|
|
3243
|
-
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme,
|
|
3178
|
+
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, onBrandClick: handleOpenKanbanFromBrand, meta: (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "Tasks assigned to me", children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("span", { className: `${styles.taskCountBadge} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''}`.trim(), title: "Tasks overdue", children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: openSyncStatusModal, title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
|
|
3244
3179
|
marginLeft: '6px',
|
|
3245
3180
|
height: '24px',
|
|
3246
3181
|
width: '24px',
|
|
@@ -3252,10 +3187,29 @@ export function StandaloneLayout(props) {
|
|
|
3252
3187
|
display: 'inline-flex',
|
|
3253
3188
|
alignItems: 'center',
|
|
3254
3189
|
justifyContent: 'center'
|
|
3255
|
-
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: () => { void
|
|
3190
|
+
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsxs(_Fragment, { children: [isAuthenticated && accountProfileSummary?.stripeCustomerId && (_jsx("button", { className: "tf-control-icon", onClick: () => { void handleOpenBillingPortal(); }, title: "Manage billing", disabled: billingActionBusy, children: "Manage billing" })), _jsx("button", { className: "tf-control-icon", onClick: () => {
|
|
3191
|
+
if (hasActiveCommercialEntitlement) {
|
|
3192
|
+
void handlePlansBack();
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
closePlansScreen();
|
|
3196
|
+
}, title: hasActiveCommercialEntitlement ? 'Continue to Taskforce' : 'Back', disabled: plansNavigationBusy, children: hasActiveCommercialEntitlement ? 'Take Me to Taskforce' : 'Back' })] })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: {
|
|
3197
|
+
position: 'relative',
|
|
3198
|
+
display: 'flex',
|
|
3199
|
+
flex: 1,
|
|
3200
|
+
minHeight: 0,
|
|
3201
|
+
overflowY: 'auto',
|
|
3202
|
+
overflowX: 'hidden',
|
|
3203
|
+
scrollbarGutter: 'stable',
|
|
3204
|
+
background: 'var(--surface-page)'
|
|
3205
|
+
}, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true, onAccountProfileSummaryChange: applyPlansAccountProfileSummary, onContinueToTaskforce: handlePlansBack, continueBusy: plansNavigationBusy }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, documentTrayOpen: documentTrayOpen, onCloseDocumentTray: () => setDocumentTrayOpen(false), requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
|
|
3256
3206
|
setRequestedDocPath(null);
|
|
3257
3207
|
setRequestedDocAssetId(null);
|
|
3258
|
-
}, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId
|
|
3208
|
+
}, onBackToTask: handleBackToTaskFromDocument, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, imageTrayOpen: imageTrayOpen, onCloseImageTray: () => setImageTrayOpen(false), resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId, cloudAuthConfigured: cloudAuthConfigured, authSessionResolved: authSessionResolved, isAuthenticated: isAuthenticated, cloudAiProfileSeatUsage: accountProfileSummary?.aiProfileSeatUsage ?? null, agentTrayOpen: agentTrayOpen, onCloseAgentTray: () => setAgentTrayOpen(false), mcpSettingsNode: _jsx(TaskSettings, { settingsModel: {
|
|
3209
|
+
...settingsModel,
|
|
3210
|
+
initialSection: 'mcp',
|
|
3211
|
+
onOpenCloudAuth: openSharedLoginRoute
|
|
3212
|
+
}, renderMode: "mcp-only" }) }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
|
|
3259
3213
|
position: 'relative',
|
|
3260
3214
|
opacity: props.loadingTasks ? 0.7 : 1,
|
|
3261
3215
|
transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
@@ -3266,12 +3220,12 @@ export function StandaloneLayout(props) {
|
|
|
3266
3220
|
? `${WORKSPACE_TOOL_RAIL_WIDTH + planningDrawerWidthPx + WORKSPACE_CONTENT_GUTTER}px`
|
|
3267
3221
|
: `${WORKSPACE_TOOL_RAIL_WIDTH + WORKSPACE_CONTENT_GUTTER}px`,
|
|
3268
3222
|
paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) ? `${SCHEDULE_SIDEBAR_WIDTH_PX}px` : 0
|
|
3269
|
-
}, children: [workspaceToolRail, _jsx("div", { ref:
|
|
3223
|
+
}, children: [workspaceToolRail, _jsx("div", { ref: setPlanningDrawerPortalRef, style: {
|
|
3270
3224
|
position: 'absolute',
|
|
3271
3225
|
inset: 0,
|
|
3272
3226
|
zIndex: 35,
|
|
3273
3227
|
pointerEvents: 'none',
|
|
3274
|
-
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
|
|
3228
|
+
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, filtersReady: hasInitedFilters, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
|
|
3275
3229
|
setScheduleSelectedDate(dateOnly);
|
|
3276
3230
|
const parsed = parseDateOnlyLocal(dateOnly);
|
|
3277
3231
|
if (parsed) {
|
|
@@ -3281,8 +3235,8 @@ export function StandaloneLayout(props) {
|
|
|
3281
3235
|
if (groupBy !== 'schedule')
|
|
3282
3236
|
return;
|
|
3283
3237
|
setScheduleScrollLeft(scrollLeft);
|
|
3284
|
-
}, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, readOnlyMode: taskScope === 'deleted' ? 'deleted' : taskScope === 'archived' ? 'archived' : null, recentlyChangedTaskIds: props.recentlyChangedTaskIds, sortBy: sortBy, sortOrder: props.sortOrder, planningDropTargets:
|
|
3285
|
-
? createPortal(planningDrawerNode,
|
|
3238
|
+
}, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, readOnlyMode: taskScope === 'deleted' ? 'deleted' : taskScope === 'archived' ? 'archived' : null, recentlyChangedTaskIds: props.recentlyChangedTaskIds, sortBy: sortBy, sortOrder: props.sortOrder, planningDropTargets: planningDrawerPortalElement
|
|
3239
|
+
? createPortal(planningDrawerNode, planningDrawerPortalElement)
|
|
3286
3240
|
: null, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAssignWorkstreamToInitiative: handleAssignInitiativeToWorkstream, workstreams: props.workstreams, initiatives: props.initiatives, onUnarchive: (taskId) => {
|
|
3287
3241
|
if (taskScope === 'deleted') {
|
|
3288
3242
|
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
@@ -3338,41 +3292,17 @@ export function StandaloneLayout(props) {
|
|
|
3338
3292
|
setSettingsSection(section);
|
|
3339
3293
|
setActiveTab('settings');
|
|
3340
3294
|
}, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
|
|
3341
|
-
|
|
3342
|
-
setAttachments((prev) => {
|
|
3343
|
-
nextAttachments = [...prev, file];
|
|
3344
|
-
return nextAttachments;
|
|
3345
|
-
});
|
|
3346
|
-
setAttachmentsDirty(true);
|
|
3347
|
-
if (editingTaskId) {
|
|
3348
|
-
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
3349
|
-
}
|
|
3295
|
+
updateTaskContextAttachments((current) => [...current, file]);
|
|
3350
3296
|
}, onRemoveContextFile: async (index) => {
|
|
3351
|
-
|
|
3352
|
-
setAttachments((prev) => {
|
|
3353
|
-
nextAttachments = prev.filter((_, i) => i !== index);
|
|
3354
|
-
return nextAttachments;
|
|
3355
|
-
});
|
|
3356
|
-
setAttachmentsDirty(true);
|
|
3357
|
-
if (editingTaskId) {
|
|
3358
|
-
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
3359
|
-
}
|
|
3297
|
+
updateTaskContextAttachments((current) => current.filter((_, i) => i !== index));
|
|
3360
3298
|
}, onUpdateContextCaption: (index, caption) => {
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
return { ...s, caption };
|
|
3369
|
-
});
|
|
3370
|
-
return nextAttachments;
|
|
3371
|
-
});
|
|
3372
|
-
setAttachmentsDirty(true);
|
|
3373
|
-
if (editingTaskId) {
|
|
3374
|
-
void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
|
|
3375
|
-
}
|
|
3299
|
+
updateTaskContextAttachments((current) => current.map((s, i) => {
|
|
3300
|
+
if (i !== index)
|
|
3301
|
+
return s;
|
|
3302
|
+
if (typeof s === 'string')
|
|
3303
|
+
return { path: s, caption, timestamp: new Date().toISOString() };
|
|
3304
|
+
return { ...s, caption };
|
|
3305
|
+
}));
|
|
3376
3306
|
}, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onUnarchive: (taskId) => {
|
|
3377
3307
|
if (currentTask?.isDeleted) {
|
|
3378
3308
|
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
@@ -3382,11 +3312,18 @@ export function StandaloneLayout(props) {
|
|
|
3382
3312
|
return;
|
|
3383
3313
|
}
|
|
3384
3314
|
void handleUnarchive(taskId);
|
|
3385
|
-
}, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel:
|
|
3315
|
+
}, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: {
|
|
3316
|
+
...settingsModel,
|
|
3317
|
+
onOpenCloudAuth: openSharedLoginRoute
|
|
3318
|
+
}, onSectionChange: setSettingsSection }) }) }), _jsx(ScheduleWarningModal, { prompt: scheduleWarningPrompt, theme: currentTheme, onClose: cancelScheduleWarning, onConfirm: confirmScheduleWarning }), _jsx(SyncEnableWarningModal, { isOpen: showSyncEnableWarning, theme: currentTheme, onClose: cancelSyncEnableWarning, onConfirm: confirmSyncEnableWarning }), _jsx(HelpModal, { isOpen: showHelpModal, theme: currentTheme, onClose: () => setShowHelpModal(false), onOpenSettings: () => {
|
|
3386
3319
|
setShowHelpModal(false);
|
|
3387
3320
|
setActiveTab('settings');
|
|
3388
3321
|
setSettingsSection('general');
|
|
3389
|
-
} }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial,
|
|
3322
|
+
} }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onOpenAvatarManager: () => {
|
|
3323
|
+
setProfileSaveError(null);
|
|
3324
|
+
setProfileSaveNotice(null);
|
|
3325
|
+
setShowAvatarPhotoManager(true);
|
|
3326
|
+
} }), _jsx(AvatarImageManagerModal, { isOpen: showAvatarPhotoManager, theme: currentTheme, title: "Edit Profile Photo", currentImageUrl: profileAvatarDisplayUrl, fallbackInitial: accountBadgeInitial, accept: PROFILE_AVATAR_ACCEPT, busy: profileAvatarBusy, hasPendingImage: Boolean(profileAvatarDraftId), canRemove: Boolean(profileAvatarDisplayUrl), error: profileSaveError, notice: profileSaveNotice, onClose: () => setShowAvatarPhotoManager(false), onApplyImage: handleProfileAvatarSelected, onRemoveImage: handleRemoveProfileAvatar, onDiscardPendingImage: handleDiscardPendingProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice, accountProfileSummary: accountProfileSummary, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, cloudAuthEnabled: cloudAuthConfigured, availableAuthProviders: availableAuthProviders, onFetchLoginMethods: fetchLoginMethods, onUnlinkLoginMethod: unlinkLoginMethod, onAddPassword: addPasswordToAccount, onLinkProvider: (provider) => beginOAuthLink(provider), onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
|
|
3390
3327
|
setShowAccountHub(false);
|
|
3391
3328
|
void openTeamManagementModal();
|
|
3392
3329
|
setTeamManagementTab('audit');
|
|
@@ -3442,7 +3379,7 @@ export function StandaloneLayout(props) {
|
|
|
3442
3379
|
headers: teamAdminHeaders(),
|
|
3443
3380
|
credentials: 'include'
|
|
3444
3381
|
}));
|
|
3445
|
-
}, onInviteEmailChange: setTeamInviteEmail, onInviteRoleChange: setTeamInviteRole, onInvitePermissionModeChange: setTeamInvitePermissionMode, onSubmitInvite: () => { void submitTeamInvite(); }, onLoadAuditPrevious: () => { void loadTeamAuditPage(teamAuditPage - 1); }, onLoadAuditNext: () => { void loadTeamAuditPage(teamAuditPage + 1); } }), _jsx(CreateWorkspaceConfirmModal, { isOpen: showCreateWorkspaceConfirm, theme: currentTheme, onClose: () => setShowCreateWorkspaceConfirm(false), onConfirm: () => openWorkspaceSetup({ intent: 'create-workspace' }) }), _jsx(SyncStatusModal, { isOpen: showSyncStatusModal, theme: currentTheme, currentWorkspaceLabel: currentWorkspaceLabel, syncStatusMeta: syncStatusMeta, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncControlBusy: syncControlBusy, canManageWorkspaceSync: canManageWorkspaceSync, workspaceSyncSummary:
|
|
3382
|
+
}, onInviteEmailChange: setTeamInviteEmail, onInviteRoleChange: setTeamInviteRole, onInvitePermissionModeChange: setTeamInvitePermissionMode, onSubmitInvite: () => { void submitTeamInvite(); }, onLoadAuditPrevious: () => { void loadTeamAuditPage(teamAuditPage - 1); }, onLoadAuditNext: () => { void loadTeamAuditPage(teamAuditPage + 1); } }), _jsx(CreateWorkspaceConfirmModal, { isOpen: showCreateWorkspaceConfirm, theme: currentTheme, onClose: () => setShowCreateWorkspaceConfirm(false), onConfirm: () => openWorkspaceSetup({ intent: 'create-workspace' }) }), _jsx(SyncStatusModal, { isOpen: showSyncStatusModal, theme: currentTheme, currentWorkspaceLabel: currentWorkspaceLabel, syncStatusMeta: syncStatusMeta, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncControlBusy: syncControlBusy, canManageWorkspaceSync: canManageWorkspaceSync, workspaceSyncSummary: headerSyncPresentation.summary, workspaceSyncRecommendedAction: headerSyncPresentation.recommendedAction, workspaceSyncRepairBusy: workspaceSyncRepairBusy, referenceMismatchCount: referenceMismatchCount, syncStageLabel: syncStageLabel, workspaceSyncPendingChanges: workspaceSyncPendingChanges, formattedLastSyncTime: formattedLastSyncTime, formattedLastPullTime: formattedLastPullTime, formattedLastPushTime: formattedLastPushTime, syncLastError: syncLastError, workspaceSyncDiagnostics: workspaceSyncDiagnostics, activeReferenceMismatchSummaries: activeReferenceMismatchSummaries, syncDiagnosticsSummary: syncDiagnosticsSummary, syncEventRows: syncEventRows, syncEventsListRef: syncEventsListRef, workspaceSyncRepairQueued: workspaceSyncRepairQueued, workspaceSyncBusy: workspaceSyncBusy, workspaceSyncCopied: workspaceSyncCopied, runtimeMode: runtimeMode, isAuthenticated: isAuthenticated, onClose: closeSyncStatusModal, onToggleWorkspaceSync: (enabled) => {
|
|
3446
3383
|
void handleWorkspaceSyncToggle(enabled);
|
|
3447
3384
|
}, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
|
|
3448
3385
|
openSharedLoginRoute('login');
|