@taskforcehq/taskforce 0.3.305 → 0.3.306
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +215 -43
- package/dist/TaskforceCore.test.js +433 -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/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +549 -0
- package/dist/components/features/AgentsModule.d.ts +10 -1
- package/dist/components/features/AgentsModule.js +260 -15
- package/dist/components/features/AgentsModule.test.js +255 -5
- package/dist/components/features/DocumentViewer.d.ts +5 -1
- package/dist/components/features/DocumentViewer.js +60 -3
- package/dist/components/features/DocumentWorkspace.js +1 -1
- package/dist/components/features/DocumentWorkspace.test.js +33 -0
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- package/dist/components/task/TaskCard.js +19 -28
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskForm.js +85 -90
- 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 -13
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +70 -14
- package/dist/components/views/PlanComparisonPage.test.js +238 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +283 -533
- package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
- package/dist/components/views/panels/PlanningDrawer.js +18 -11
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +141 -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/core/AiProfileService.d.ts +39 -2
- package/dist/core/AiProfileService.js +639 -73
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +566 -7
- package/dist/core/AttachmentLinkService.js +57 -15
- 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 +115 -6
- 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 +52 -0
- 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/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/Taskforce.d.ts +225 -14
- package/dist/core/Taskforce.js +765 -192
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -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 +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
- 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 +10 -1
- package/dist/hooks/sync/orchestratorShared.js +68 -47
- 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 +171 -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 +15 -3
- package/dist/hooks/sync/transfers.js +179 -62
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +396 -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/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
- package/dist/hooks/useSyncOrchestrator.js +359 -178
- 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 +60 -2
- package/dist/hooks/useTaskforce.js +549 -240
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2380 -212
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +6 -1
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
- 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/documentAssetRegistrar.js +3 -3
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +131 -64
- package/dist/mcp/runtime.test.js +291 -12
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +171 -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/annotatedAttachmentsRoutes.test.js +3 -3
- 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 +120 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +106 -3
- package/dist/server/routes/admin.d.ts +3 -0
- package/dist/server/routes/admin.js +272 -19
- package/dist/server/routes/auth.d.ts +15 -2
- package/dist/server/routes/auth.js +994 -57
- package/dist/server/routes/authSupport.d.ts +1 -1
- package/dist/server/routes/authSupport.js +1 -1
- package/dist/server/routes/billing.js +361 -35
- package/dist/server/routes/billing.test.js +592 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +172 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +61 -605
- 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 +180 -0
- package/dist/server/routes/tasks.js +43 -1
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +60 -3
- package/dist/server/routes.test.js +1824 -171
- 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/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/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 +17 -11
- package/dist/sync/collaborationSyncPayload.test.js +13 -1
- package/dist/sync/contentSyncState.d.ts +4 -8
- package/dist/sync/contentSyncState.js +17 -70
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +70 -3
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +44 -0
- package/dist/sync/syncResourceAdapters.js +77 -0
- package/dist/sync/syncService.d.ts +5 -0
- package/dist/sync/syncService.js +52 -0
- package/dist/sync/syncService.test.js +57 -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.js +4 -3
- package/dist/sync/workspacePullFeed.test.js +43 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +45 -0
- package/dist/sync/workspaceSyncModel.js +183 -28
- package/dist/sync/workspaceSyncModel.test.js +291 -12
- 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 +17 -0
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BG6P_GVW.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
- 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-B03MaSkw.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
- package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-9E6ParrY.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-CvfnMiWq.css +1 -0
- package/dist/ui/assets/PlansPage-DRXscYxH.js +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/{TaskSettings-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-BA7jcEpH.js} +1 -1
- package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
- package/dist/ui/assets/index-C6k75jr8.js +6 -0
- package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-DuEd_65c.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/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/assignees.d.ts +2 -0
- package/dist/utils/assignees.js +2 -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/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 +2 -0
- package/dist/utils/taskActivity.js +228 -22
- package/dist/utils/taskActivity.test.js +119 -8
- 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/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/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,5 +1,5 @@
|
|
|
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
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';
|
|
@@ -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';
|
|
@@ -30,11 +32,13 @@ import { getTaskReferenceLabel, parseTaskReference } from '../../utils/taskRefer
|
|
|
30
32
|
import { formatInitiativeReference, formatWorkstreamReference, parseWorkstreamReference, resolveInitiativeReference } from '../../utils/planningReferences';
|
|
31
33
|
import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
|
|
32
34
|
import { getImageReferenceLabel } from '../../utils/imageReferences';
|
|
35
|
+
import { isPlanningEntityVisibleForScope, scopePlanningSummaries } from './planningScope';
|
|
36
|
+
import { buildReferenceMismatchSummaries, buildSyncDiagnosticsSummary, buildSyncEventRows, getActiveReferenceMismatchCount, resolveSyncStageLabel, resolveSyncStatusMeta, } from '../../utils/syncStatusPresentation';
|
|
33
37
|
import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
|
|
34
38
|
import { getLocale, t } from '../../localization';
|
|
35
39
|
import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
|
|
36
40
|
import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
|
|
37
|
-
import {
|
|
41
|
+
import { prepareAvatarUploadFile } from '../../utils/avatarUpload';
|
|
38
42
|
import { resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
|
|
39
43
|
import { AppShellHeader } from './AppShellHeader';
|
|
40
44
|
import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
|
|
@@ -47,6 +51,9 @@ import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider
|
|
|
47
51
|
import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
|
|
48
52
|
import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
|
|
49
53
|
import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement } from './teamManagementAccess';
|
|
54
|
+
import { useRecentSyncEvents } from '../../hooks/sync/useRecentSyncEvents';
|
|
55
|
+
import { useSyncStatusActions } from '../../hooks/sync/useSyncStatusActions';
|
|
56
|
+
import { useSyncStatusControls } from '../../hooks/sync/useSyncStatusControls';
|
|
50
57
|
import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
|
|
51
58
|
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
59
|
// Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
|
|
@@ -60,72 +67,6 @@ const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m
|
|
|
60
67
|
const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
|
|
61
68
|
const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
|
|
62
69
|
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
70
|
function resolveConnectedEnvironmentLabel(explicitEnvironment, candidates) {
|
|
130
71
|
const parsedExplicitEnvironment = parseTaskforceCloudEnvironment(explicitEnvironment);
|
|
131
72
|
if (parsedExplicitEnvironment) {
|
|
@@ -203,7 +144,7 @@ export function StandaloneLayout(props) {
|
|
|
203
144
|
// Form Actions
|
|
204
145
|
handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
|
|
205
146
|
// 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,
|
|
147
|
+
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
148
|
// filterStatus, setFilterStatus, // This was duplicated, removed one instance
|
|
208
149
|
sortBy, setSortBy, clearFilters, // setSearchQuery, // This was duplicated, removed one instance
|
|
209
150
|
// filterCategories, setFilterCategories, // This was duplicated, removed one instance
|
|
@@ -214,7 +155,7 @@ export function StandaloneLayout(props) {
|
|
|
214
155
|
// clearFilters, // This was duplicated, removed one instance
|
|
215
156
|
// activeTypes, // This was duplicated, removed one instance
|
|
216
157
|
// 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,
|
|
158
|
+
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
159
|
// Browser Props
|
|
219
160
|
showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
|
|
220
161
|
// Settings Managers
|
|
@@ -284,6 +225,9 @@ export function StandaloneLayout(props) {
|
|
|
284
225
|
const [planningDraftDescription, setPlanningDraftDescription] = useState('');
|
|
285
226
|
const [planningDraftOwner, setPlanningDraftOwner] = useState('');
|
|
286
227
|
const [planningDraftInitiativeId, setPlanningDraftInitiativeId] = useState('');
|
|
228
|
+
const [isSubmittingPlanningEditor, setIsSubmittingPlanningEditor] = useState(false);
|
|
229
|
+
const planningContextAttachmentDraftsRef = useRef(new Map());
|
|
230
|
+
const planningContextAttachmentUpdateQueueRef = useRef(new Map());
|
|
287
231
|
const [documentTypeFilters, setDocumentTypeFilters] = useState(() => DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
|
|
288
232
|
const [documentAttachmentFilters, setDocumentAttachmentFilters] = useState(() => DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
|
|
289
233
|
const [requestedDocPath, setRequestedDocPath] = useState(null);
|
|
@@ -293,6 +237,7 @@ export function StandaloneLayout(props) {
|
|
|
293
237
|
const [requestedAnnotatedOpenVersion, setRequestedAnnotatedOpenVersion] = useState(0);
|
|
294
238
|
const [canonicalAnnotatedImageReferenceLabels, setCanonicalAnnotatedImageReferenceLabels] = useState({});
|
|
295
239
|
const [layoutStateReady, setLayoutStateReady] = useState(false);
|
|
240
|
+
const planningEditorSubmitInFlightRef = useRef(false);
|
|
296
241
|
const loadedLayoutStateWorkspaceRef = useRef('');
|
|
297
242
|
const requestedAnnotatedTargetRef = useRef(null);
|
|
298
243
|
const requestedAnnotatedSessionIdRef = useRef(null);
|
|
@@ -836,84 +781,103 @@ export function StandaloneLayout(props) {
|
|
|
836
781
|
: taskScope === 'deleted'
|
|
837
782
|
? deletedScopeAllTasks
|
|
838
783
|
: props.tasks.filter((task) => !task.isArchived);
|
|
839
|
-
const
|
|
840
|
-
const taskCount = scopeTasks.length;
|
|
841
|
-
const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
|
|
784
|
+
const summarizeTaskCounts = (taskCount, completedTaskCount) => {
|
|
842
785
|
return {
|
|
843
786
|
progressPercent: taskCount > 0 ? Math.round((completedTaskCount / taskCount) * 100) : 0,
|
|
844
787
|
taskCount,
|
|
845
788
|
completedTaskCount,
|
|
846
789
|
};
|
|
847
790
|
};
|
|
791
|
+
const useBootstrapTaskSummaries = taskScope === 'open'
|
|
792
|
+
&& props.planningBootstrapTaskSummaries.length > 0
|
|
793
|
+
&& (props.loadingTasks || props.tasks.length === 0);
|
|
794
|
+
const workstreamTaskSummaryById = new Map();
|
|
795
|
+
if (useBootstrapTaskSummaries) {
|
|
796
|
+
props.planningBootstrapTaskSummaries.forEach((summary) => {
|
|
797
|
+
workstreamTaskSummaryById.set(summary.workstreamId, summary);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
planningScopedTasks.forEach((task) => {
|
|
802
|
+
const workstreamId = String(task.workstreamId || '').trim();
|
|
803
|
+
if (!workstreamId)
|
|
804
|
+
return;
|
|
805
|
+
const current = workstreamTaskSummaryById.get(workstreamId) || {
|
|
806
|
+
workstreamId,
|
|
807
|
+
taskCount: 0,
|
|
808
|
+
completedTaskCount: 0,
|
|
809
|
+
tasks: [],
|
|
810
|
+
};
|
|
811
|
+
current.taskCount += 1;
|
|
812
|
+
if (task.isArchived || task.status === 'done' || task.status === 'cancelled') {
|
|
813
|
+
current.completedTaskCount += 1;
|
|
814
|
+
}
|
|
815
|
+
current.tasks.push({
|
|
816
|
+
id: task.id,
|
|
817
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
818
|
+
title: task.title,
|
|
819
|
+
status: task.status || null,
|
|
820
|
+
});
|
|
821
|
+
workstreamTaskSummaryById.set(workstreamId, current);
|
|
822
|
+
});
|
|
823
|
+
}
|
|
848
824
|
const workstreamTaskIds = new Map();
|
|
849
825
|
const workstreamInitiativeIds = new Map();
|
|
850
826
|
const initiativeTaskIds = new Map();
|
|
851
|
-
const
|
|
852
|
-
const initiativeById = new Map();
|
|
827
|
+
const workstreamsByInitiativeId = new Map();
|
|
853
828
|
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
|
-
}));
|
|
829
|
+
const taskSummary = workstreamTaskSummaryById.get(workstream.id);
|
|
830
|
+
const taskPreviews = taskSummary?.tasks || [];
|
|
861
831
|
const summary = {
|
|
862
832
|
id: workstream.id,
|
|
863
833
|
referenceNumber: workstream.referenceNumber ?? null,
|
|
864
834
|
title: workstream.title,
|
|
865
835
|
description: String(workstream.description || '').trim() || undefined,
|
|
866
836
|
ownerLabel: workstream.ownerId ? assigneeLabelByValue.get(String(workstream.ownerId)) || String(workstream.ownerId) : null,
|
|
867
|
-
...
|
|
837
|
+
...summarizeTaskCounts(taskSummary?.taskCount || 0, taskSummary?.completedTaskCount || 0),
|
|
868
838
|
initiativeId: workstream.initiativeId || null,
|
|
869
839
|
commentCount: Array.isArray(workstream.comments) ? workstream.comments.length : 0,
|
|
870
840
|
attachmentCount: Array.isArray(workstream.attachments) ? workstream.attachments.length : 0,
|
|
841
|
+
attachments: Array.isArray(workstream.attachments) ? workstream.attachments : [],
|
|
871
842
|
isArchived: Boolean(workstream.isArchived),
|
|
872
843
|
tasks: taskPreviews,
|
|
873
844
|
};
|
|
874
|
-
workstreamTaskIds.set(workstream.id,
|
|
875
|
-
if (workstream.initiativeId)
|
|
845
|
+
workstreamTaskIds.set(workstream.id, taskPreviews.map((task) => task.id));
|
|
846
|
+
if (workstream.initiativeId) {
|
|
876
847
|
workstreamInitiativeIds.set(workstream.id, workstream.initiativeId);
|
|
877
|
-
|
|
848
|
+
const current = workstreamsByInitiativeId.get(workstream.initiativeId) || [];
|
|
849
|
+
current.push(summary);
|
|
850
|
+
workstreamsByInitiativeId.set(workstream.initiativeId, current);
|
|
851
|
+
}
|
|
878
852
|
return summary;
|
|
879
853
|
};
|
|
880
854
|
const allWorkstreamSummaries = props.workstreams.map(buildWorkstreamSummary);
|
|
855
|
+
const scopedWorkstreamSummaries = allWorkstreamSummaries.filter((workstream) => (isPlanningEntityVisibleForScope(taskScope, Boolean(workstream.isArchived))));
|
|
881
856
|
const allInitiatives = props.initiatives.map((initiative) => {
|
|
882
|
-
const workstreams =
|
|
883
|
-
.filter((workstream) => (workstream.
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
.map((taskId) => hierarchyTaskById.get(taskId))
|
|
887
|
-
.filter((task) => Boolean(task));
|
|
857
|
+
const workstreams = (workstreamsByInitiativeId.get(initiative.id) || [])
|
|
858
|
+
.filter((workstream) => isPlanningEntityVisibleForScope(taskScope, Boolean(workstream.isArchived)));
|
|
859
|
+
const taskCount = workstreams.reduce((sum, workstream) => sum + workstream.taskCount, 0);
|
|
860
|
+
const completedTaskCount = workstreams.reduce((sum, workstream) => sum + workstream.completedTaskCount, 0);
|
|
888
861
|
const initiativeSummary = {
|
|
889
862
|
id: initiative.id,
|
|
890
863
|
referenceNumber: initiative.referenceNumber ?? null,
|
|
891
864
|
title: initiative.title,
|
|
892
865
|
description: String(initiative.description || '').trim() || undefined,
|
|
893
866
|
ownerLabel: initiative.ownerId ? assigneeLabelByValue.get(String(initiative.ownerId)) || String(initiative.ownerId) : null,
|
|
894
|
-
...
|
|
867
|
+
...summarizeTaskCounts(taskCount, completedTaskCount),
|
|
895
868
|
workstreamCount: workstreams.length,
|
|
896
869
|
workstreams,
|
|
897
870
|
commentCount: Array.isArray(initiative.comments) ? initiative.comments.length : 0,
|
|
898
871
|
attachmentCount: Array.isArray(initiative.attachments) ? initiative.attachments.length : 0,
|
|
872
|
+
attachments: Array.isArray(initiative.attachments) ? initiative.attachments : [],
|
|
899
873
|
isArchived: Boolean(initiative.isArchived),
|
|
900
874
|
};
|
|
901
|
-
initiativeTaskIds.set(initiative.id, initiativeScopedTasks.map((task) => task.id));
|
|
902
|
-
initiativeById.set(initiativeSummary.id, initiativeSummary);
|
|
903
875
|
return initiativeSummary;
|
|
904
876
|
});
|
|
905
|
-
const initiatives =
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
: !initiative.isArchived);
|
|
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);
|
|
877
|
+
const { initiatives, standaloneWorkstreams, visibleWorkstreams, initiativeById, workstreamById, } = scopePlanningSummaries(allInitiatives, scopedWorkstreamSummaries, taskScope);
|
|
878
|
+
initiatives.forEach((initiative) => {
|
|
879
|
+
initiativeTaskIds.set(initiative.id, initiative.workstreams.flatMap((workstream) => (workstream.tasks || []).map((task) => task.id)));
|
|
880
|
+
});
|
|
917
881
|
return {
|
|
918
882
|
initiatives,
|
|
919
883
|
standaloneWorkstreams,
|
|
@@ -923,7 +887,17 @@ export function StandaloneLayout(props) {
|
|
|
923
887
|
workstreamById,
|
|
924
888
|
initiativeById,
|
|
925
889
|
};
|
|
926
|
-
}, [
|
|
890
|
+
}, [
|
|
891
|
+
archivedScopeAllTasks,
|
|
892
|
+
assigneeLabelByValue,
|
|
893
|
+
deletedScopeAllTasks,
|
|
894
|
+
props.initiatives,
|
|
895
|
+
props.loadingTasks,
|
|
896
|
+
props.planningBootstrapTaskSummaries,
|
|
897
|
+
props.tasks,
|
|
898
|
+
props.workstreams,
|
|
899
|
+
taskScope,
|
|
900
|
+
]);
|
|
927
901
|
const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
|
|
928
902
|
const referenceLabel = formatInitiativeReference(initiative);
|
|
929
903
|
return {
|
|
@@ -1198,17 +1172,7 @@ export function StandaloneLayout(props) {
|
|
|
1198
1172
|
const [showAccountMenu, setShowAccountMenu] = useState(false);
|
|
1199
1173
|
const [showAccountHub, setShowAccountHub] = useState(false);
|
|
1200
1174
|
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);
|
|
1175
|
+
const [showAvatarPhotoManager, setShowAvatarPhotoManager] = useState(false);
|
|
1212
1176
|
const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
|
|
1213
1177
|
const [workspaceActionError, setWorkspaceActionError] = useState('');
|
|
1214
1178
|
const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
|
|
@@ -1249,7 +1213,6 @@ export function StandaloneLayout(props) {
|
|
|
1249
1213
|
const [profileSaveNotice, setProfileSaveNotice] = useState(null);
|
|
1250
1214
|
const TEAM_AUDIT_PAGE_SIZE = 25;
|
|
1251
1215
|
const accountMenuRef = useRef(null);
|
|
1252
|
-
const profileAvatarInputRef = useRef(null);
|
|
1253
1216
|
const accountSurfaceOpenStartedAtRef = useRef(null);
|
|
1254
1217
|
const accountSurfacePendingRef = useRef({
|
|
1255
1218
|
billing: false,
|
|
@@ -1274,21 +1237,11 @@ export function StandaloneLayout(props) {
|
|
|
1274
1237
|
const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
|
|
1275
1238
|
const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
|
|
1276
1239
|
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
|
-
}, [
|
|
1240
|
+
const { showSyncStatusModal, showSyncEnableWarning, workspaceSyncError, syncControlBusy, openSyncStatusModal, closeSyncStatusModal, handleWorkspaceSyncToggle, cancelSyncEnableWarning, confirmSyncEnableWarning, } = useSyncStatusControls({
|
|
1288
1241
|
canManageWorkspaceSync,
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1242
|
+
workspaceCloudSyncEnabled,
|
|
1243
|
+
saveWorkspaceCloudSyncSettings,
|
|
1244
|
+
});
|
|
1292
1245
|
const currentWorkspaceLabel = useMemo(() => {
|
|
1293
1246
|
const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
|
|
1294
1247
|
const namedWorkspace = String(current?.name || '').trim();
|
|
@@ -1348,23 +1301,21 @@ export function StandaloneLayout(props) {
|
|
|
1348
1301
|
if (!normalizedDraftId || !cloudAuthConfigured)
|
|
1349
1302
|
return;
|
|
1350
1303
|
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', {
|
|
1304
|
+
await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/discard'), {
|
|
1355
1305
|
method: 'POST',
|
|
1356
1306
|
headers: { 'Content-Type': 'application/json' },
|
|
1357
1307
|
credentials: 'include',
|
|
1358
1308
|
body: JSON.stringify({ draftId: normalizedDraftId })
|
|
1359
|
-
}
|
|
1309
|
+
});
|
|
1360
1310
|
}
|
|
1361
1311
|
catch {
|
|
1362
1312
|
// Best-effort cleanup only.
|
|
1363
1313
|
}
|
|
1364
|
-
}, [cloudAuthConfigured,
|
|
1314
|
+
}, [cloudAuthConfigured, resolveCloudAuthUrl]);
|
|
1365
1315
|
const closeEditProfileModal = useCallback(() => {
|
|
1366
1316
|
const pendingDraftId = profileAvatarDraftId;
|
|
1367
1317
|
setShowEditProfileModal(false);
|
|
1318
|
+
setShowAvatarPhotoManager(false);
|
|
1368
1319
|
setProfileSaveError(null);
|
|
1369
1320
|
setProfileSaveNotice(null);
|
|
1370
1321
|
setProfileAvatarDraftId(null);
|
|
@@ -1375,47 +1326,53 @@ export function StandaloneLayout(props) {
|
|
|
1375
1326
|
}, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
|
|
1376
1327
|
const handleProfileAvatarSelected = useCallback(async (file) => {
|
|
1377
1328
|
if (!file || !cloudAuthConfigured)
|
|
1378
|
-
return;
|
|
1329
|
+
return false;
|
|
1379
1330
|
if (!file.type.startsWith('image/')) {
|
|
1380
1331
|
setProfileSaveError('Profile photo must be an image file.');
|
|
1381
1332
|
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;
|
|
1333
|
+
return false;
|
|
1388
1334
|
}
|
|
1389
1335
|
setProfileAvatarBusy(true);
|
|
1390
1336
|
setProfileSaveError(null);
|
|
1391
1337
|
setProfileSaveNotice(null);
|
|
1392
1338
|
try {
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1339
|
+
const preparedAvatar = await prepareAvatarUploadFile(file, {
|
|
1340
|
+
maxBytes: PROFILE_AVATAR_MAX_BYTES
|
|
1341
|
+
});
|
|
1342
|
+
if (preparedAvatar.exceededLimit) {
|
|
1343
|
+
const message = file.type === 'image/gif'
|
|
1344
|
+
? 'Animated GIF profile photos must be 5 MB or smaller.'
|
|
1345
|
+
: 'Profile photo must be 5 MB or smaller.';
|
|
1346
|
+
throw new Error(message);
|
|
1347
|
+
}
|
|
1348
|
+
const uploadFile = preparedAvatar.file;
|
|
1349
|
+
const initRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/init'), {
|
|
1397
1350
|
method: 'POST',
|
|
1398
1351
|
headers: { 'Content-Type': 'application/json' },
|
|
1399
1352
|
credentials: 'include',
|
|
1400
1353
|
body: JSON.stringify({
|
|
1401
|
-
originalName:
|
|
1402
|
-
mimeType:
|
|
1403
|
-
size:
|
|
1354
|
+
originalName: uploadFile.name,
|
|
1355
|
+
mimeType: uploadFile.type,
|
|
1356
|
+
size: uploadFile.size
|
|
1404
1357
|
})
|
|
1405
|
-
}
|
|
1358
|
+
});
|
|
1406
1359
|
const initData = await initRes.json().catch(() => ({}));
|
|
1407
|
-
if (!initRes.ok || !initData?.success || typeof initData?.
|
|
1360
|
+
if (!initRes.ok || !initData?.success || typeof initData?.draftId !== 'string' || typeof initData?.relativePath !== 'string') {
|
|
1408
1361
|
throw new Error(initData?.error || 'Failed to start avatar upload.');
|
|
1409
1362
|
}
|
|
1410
|
-
const uploadRes = await fetch(
|
|
1411
|
-
method:
|
|
1412
|
-
headers:
|
|
1413
|
-
|
|
1363
|
+
const uploadRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/upload'), {
|
|
1364
|
+
method: 'POST',
|
|
1365
|
+
headers: {
|
|
1366
|
+
'Content-Type': uploadFile.type || 'application/octet-stream',
|
|
1367
|
+
'x-taskforce-avatar-draft-id': initData.draftId
|
|
1368
|
+
},
|
|
1369
|
+
credentials: 'include',
|
|
1370
|
+
body: uploadFile
|
|
1414
1371
|
});
|
|
1415
1372
|
if (!uploadRes.ok) {
|
|
1416
1373
|
throw new Error('Failed to upload avatar.');
|
|
1417
1374
|
}
|
|
1418
|
-
const finalizeRes = await
|
|
1375
|
+
const finalizeRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/finalize'), {
|
|
1419
1376
|
method: 'POST',
|
|
1420
1377
|
headers: { 'Content-Type': 'application/json' },
|
|
1421
1378
|
credentials: 'include',
|
|
@@ -1423,7 +1380,7 @@ export function StandaloneLayout(props) {
|
|
|
1423
1380
|
draftId: initData.draftId,
|
|
1424
1381
|
relativePath: initData.relativePath
|
|
1425
1382
|
})
|
|
1426
|
-
}
|
|
1383
|
+
});
|
|
1427
1384
|
const finalizeData = await finalizeRes.json().catch(() => ({}));
|
|
1428
1385
|
if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
|
|
1429
1386
|
throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
|
|
@@ -1431,27 +1388,24 @@ export function StandaloneLayout(props) {
|
|
|
1431
1388
|
const previousDraftId = profileAvatarDraftId;
|
|
1432
1389
|
setProfileAvatarDraftId(finalizeData.draftId);
|
|
1433
1390
|
setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
|
|
1434
|
-
setProfileSaveNotice(
|
|
1391
|
+
setProfileSaveNotice(null);
|
|
1435
1392
|
if (previousDraftId && previousDraftId !== finalizeData.draftId) {
|
|
1436
1393
|
void discardProfileAvatarDraft(previousDraftId);
|
|
1437
1394
|
}
|
|
1395
|
+
return true;
|
|
1438
1396
|
}
|
|
1439
1397
|
catch (error) {
|
|
1440
1398
|
setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
|
|
1399
|
+
return false;
|
|
1441
1400
|
}
|
|
1442
1401
|
finally {
|
|
1443
1402
|
setProfileAvatarBusy(false);
|
|
1444
|
-
if (profileAvatarInputRef.current) {
|
|
1445
|
-
profileAvatarInputRef.current.value = '';
|
|
1446
|
-
}
|
|
1447
1403
|
}
|
|
1448
1404
|
}, [
|
|
1449
1405
|
cloudAuthConfigured,
|
|
1450
1406
|
discardProfileAvatarDraft,
|
|
1451
1407
|
profileAvatarDraftId,
|
|
1452
|
-
|
|
1453
|
-
props.config?.cloudAuthBaseUrl,
|
|
1454
|
-
runtimeMode
|
|
1408
|
+
resolveCloudAuthUrl
|
|
1455
1409
|
]);
|
|
1456
1410
|
const handleDiscardPendingProfileAvatar = useCallback(() => {
|
|
1457
1411
|
if (!profileAvatarDraftId)
|
|
@@ -1497,6 +1451,7 @@ export function StandaloneLayout(props) {
|
|
|
1497
1451
|
setProfileAvatarDraftId(null);
|
|
1498
1452
|
setProfileAvatarPreviewUrl('');
|
|
1499
1453
|
setProfileSaveBusy(false);
|
|
1454
|
+
setShowAvatarPhotoManager(false);
|
|
1500
1455
|
setShowEditProfileModal(false);
|
|
1501
1456
|
}, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
|
|
1502
1457
|
const teamAdminHeaders = useCallback(() => {
|
|
@@ -1815,9 +1770,9 @@ export function StandaloneLayout(props) {
|
|
|
1815
1770
|
const target = (!targetRaw || targetRaw === '/login' || !targetRaw.startsWith('/')) ? '/' : targetRaw;
|
|
1816
1771
|
setShowAccountMenu(false);
|
|
1817
1772
|
setShowAccountHub(false);
|
|
1818
|
-
|
|
1773
|
+
closeSyncStatusModal();
|
|
1819
1774
|
navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
|
|
1820
|
-
}, [location.hash, location.pathname, location.search, navigate]);
|
|
1775
|
+
}, [closeSyncStatusModal, location.hash, location.pathname, location.search, navigate]);
|
|
1821
1776
|
const openWorkspaceSetup = useCallback((options) => {
|
|
1822
1777
|
setShowAccountMenu(false);
|
|
1823
1778
|
setShowCreateWorkspaceConfirm(false);
|
|
@@ -1830,37 +1785,44 @@ export function StandaloneLayout(props) {
|
|
|
1830
1785
|
const handlePlansBack = useCallback(async () => {
|
|
1831
1786
|
if (plansNavigationBusy)
|
|
1832
1787
|
return;
|
|
1788
|
+
setPlansNavigationBusy(true);
|
|
1833
1789
|
if (!isAuthenticated) {
|
|
1834
|
-
|
|
1790
|
+
try {
|
|
1791
|
+
closePlansScreen();
|
|
1792
|
+
}
|
|
1793
|
+
finally {
|
|
1794
|
+
setPlansNavigationBusy(false);
|
|
1795
|
+
}
|
|
1835
1796
|
return;
|
|
1836
1797
|
}
|
|
1837
|
-
const
|
|
1838
|
-
if (!resolveCommercialLifecyclePresentation(
|
|
1798
|
+
const currentSummary = accountProfileSummaryRef.current;
|
|
1799
|
+
if (currentSummary && !resolveCommercialLifecyclePresentation(currentSummary).allowReturnToApp) {
|
|
1800
|
+
setPlansNavigationBusy(false);
|
|
1839
1801
|
return;
|
|
1840
1802
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1803
|
+
closePlansScreen();
|
|
1804
|
+
void (async () => {
|
|
1805
|
+
try {
|
|
1806
|
+
const result = await props.continueAfterCommercialOnboarding();
|
|
1807
|
+
if (!result.success) {
|
|
1808
|
+
pushNotice(result.error || 'Unable to finish onboarding.', 'error');
|
|
1809
|
+
if (result.destination === 'login') {
|
|
1810
|
+
navigate('/login', { replace: true });
|
|
1811
|
+
}
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (result.destination === 'setup') {
|
|
1815
|
+
openWorkspaceSetup();
|
|
1848
1816
|
}
|
|
1849
|
-
return;
|
|
1850
1817
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
return;
|
|
1818
|
+
finally {
|
|
1819
|
+
setPlansNavigationBusy(false);
|
|
1854
1820
|
}
|
|
1855
|
-
|
|
1856
|
-
}
|
|
1857
|
-
finally {
|
|
1858
|
-
setPlansNavigationBusy(false);
|
|
1859
|
-
}
|
|
1821
|
+
})();
|
|
1860
1822
|
}, [
|
|
1823
|
+
accountProfileSummaryRef,
|
|
1861
1824
|
closePlansScreen,
|
|
1862
1825
|
isAuthenticated,
|
|
1863
|
-
loadAccountProfileSummary,
|
|
1864
1826
|
navigate,
|
|
1865
1827
|
openWorkspaceSetup,
|
|
1866
1828
|
plansNavigationBusy,
|
|
@@ -2046,6 +2008,7 @@ export function StandaloneLayout(props) {
|
|
|
2046
2008
|
const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
|
|
2047
2009
|
const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
|
|
2048
2010
|
const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
|
|
2011
|
+
const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
|
|
2049
2012
|
const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
|
|
2050
2013
|
runtimeMode,
|
|
2051
2014
|
isAuthenticated,
|
|
@@ -2053,7 +2016,10 @@ export function StandaloneLayout(props) {
|
|
|
2053
2016
|
workspaceCloudSyncEnabled,
|
|
2054
2017
|
workspaceSyncSummary,
|
|
2055
2018
|
workspaceSyncRecommendedAction,
|
|
2056
|
-
workspaceSyncError: rawSyncLastError
|
|
2019
|
+
workspaceSyncError: rawSyncLastError,
|
|
2020
|
+
pushBlockedReason: workspaceSyncDiagnostics.pushBlockedReason || null,
|
|
2021
|
+
pullBlockedReason: workspaceSyncDiagnostics.pullBlockedReason || null,
|
|
2022
|
+
retryBlockedReason: workspaceSyncDiagnostics.retryBlockedReason || null
|
|
2057
2023
|
}), [
|
|
2058
2024
|
runtimeMode,
|
|
2059
2025
|
isAuthenticated,
|
|
@@ -2061,75 +2027,24 @@ export function StandaloneLayout(props) {
|
|
|
2061
2027
|
workspaceCloudSyncEnabled,
|
|
2062
2028
|
workspaceSyncSummary,
|
|
2063
2029
|
workspaceSyncRecommendedAction,
|
|
2064
|
-
rawSyncLastError
|
|
2030
|
+
rawSyncLastError,
|
|
2031
|
+
workspaceSyncDiagnostics.pushBlockedReason,
|
|
2032
|
+
workspaceSyncDiagnostics.pullBlockedReason,
|
|
2033
|
+
workspaceSyncDiagnostics.retryBlockedReason
|
|
2065
2034
|
]);
|
|
2066
2035
|
const syncLastError = headerSyncPresentation.lastError;
|
|
2067
2036
|
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]);
|
|
2037
|
+
const syncDiagnosticsSummary = useMemo(() => buildSyncDiagnosticsSummary(workspaceSyncDiagnostics), [workspaceSyncDiagnostics]);
|
|
2038
|
+
const { syncRecentEvents, syncRecentEventsLoading, syncRecentEventsError, syncEventsListRef, loadRecentSyncEvents, } = useRecentSyncEvents({
|
|
2039
|
+
isOpen: showSyncStatusModal,
|
|
2040
|
+
workspaceId: currentWorkspaceId,
|
|
2041
|
+
refreshKeys: [
|
|
2042
|
+
workspaceLastPushAt,
|
|
2043
|
+
workspaceLastPullAt,
|
|
2044
|
+
workspaceLastErrorAt,
|
|
2045
|
+
workspaceSyncStatus
|
|
2046
|
+
]
|
|
2047
|
+
});
|
|
2133
2048
|
const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
|
|
2134
2049
|
&& workspaceSyncStatus === 'syncing'
|
|
2135
2050
|
&& workspaceSyncPhase === 'active'
|
|
@@ -2144,282 +2059,34 @@ export function StandaloneLayout(props) {
|
|
|
2144
2059
|
}, 1200);
|
|
2145
2060
|
return () => window.clearTimeout(timeoutId);
|
|
2146
2061
|
}, [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]);
|
|
2062
|
+
const syncEventRows = useMemo(() => buildSyncEventRows({
|
|
2063
|
+
syncRecentEvents,
|
|
2064
|
+
syncRecentEventsError,
|
|
2065
|
+
syncRecentEventsLoading
|
|
2066
|
+
}), [syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
|
|
2200
2067
|
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
|
-
}, [
|
|
2068
|
+
const activeReferenceMismatchSummaries = useMemo(() => buildReferenceMismatchSummaries(syncRecentEvents), [syncRecentEvents]);
|
|
2069
|
+
const syncStatusMeta = useMemo(() => resolveSyncStatusMeta(headerSyncStatus), [headerSyncStatus]);
|
|
2070
|
+
const { workspaceSyncRepairBusy, workspaceSyncRepairQueued, workspaceSyncCopied, handleCopySyncDetails, handleQueueOrRunRepairSync, } = useSyncStatusActions({
|
|
2381
2071
|
currentWorkspaceId,
|
|
2382
|
-
syncStatusMeta.label,
|
|
2383
|
-
headerSyncPresentation.summary,
|
|
2072
|
+
syncStatusLabel: syncStatusMeta.label,
|
|
2073
|
+
workspaceSyncSummary: headerSyncPresentation.summary,
|
|
2074
|
+
workspaceSyncRecommendedAction: headerSyncPresentation.recommendedAction,
|
|
2384
2075
|
formattedLastSyncTime,
|
|
2385
2076
|
formattedLastPullTime,
|
|
2386
2077
|
formattedLastPushTime,
|
|
2387
2078
|
workspaceSyncDiagnostics,
|
|
2388
2079
|
workspaceSyncPendingChanges,
|
|
2389
2080
|
syncLastError,
|
|
2390
|
-
|
|
2081
|
+
workspaceSyncPhase,
|
|
2391
2082
|
referenceMismatchCount,
|
|
2392
|
-
pushNotice,
|
|
2393
2083
|
syncRecentEvents,
|
|
2394
2084
|
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]);
|
|
2085
|
+
pushNotice,
|
|
2086
|
+
resetWorkspaceSyncCursorAndPull,
|
|
2087
|
+
workspaceSyncBusy,
|
|
2088
|
+
});
|
|
2089
|
+
const syncStageLabel = useMemo(() => resolveSyncStageLabel(workspaceSyncPhase, workspaceSyncRepairBusy, workspaceSyncBusy), [workspaceSyncBusy, workspaceSyncPhase, workspaceSyncRepairBusy]);
|
|
2423
2090
|
const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
|
|
2424
2091
|
const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
|
|
2425
2092
|
const scheduleDates = useMemo(() => {
|
|
@@ -2640,6 +2307,8 @@ export function StandaloneLayout(props) {
|
|
|
2640
2307
|
setActiveTab('add');
|
|
2641
2308
|
}, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
|
|
2642
2309
|
const openPlanningEditor = useCallback((editor) => {
|
|
2310
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2311
|
+
setIsSubmittingPlanningEditor(false);
|
|
2643
2312
|
setPlanningEditor(editor);
|
|
2644
2313
|
setPlanningPrimaryCollapsed(false);
|
|
2645
2314
|
setPlanningSecondaryEditor(null);
|
|
@@ -2654,6 +2323,8 @@ export function StandaloneLayout(props) {
|
|
|
2654
2323
|
setPlanningNestedWorkstreamDetailId(null);
|
|
2655
2324
|
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2656
2325
|
const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
|
|
2326
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2327
|
+
setIsSubmittingPlanningEditor(false);
|
|
2657
2328
|
setPlanningSecondaryEditor(editor);
|
|
2658
2329
|
setPlanningSecondaryCollapsed(false);
|
|
2659
2330
|
setPlanningDraftTitle('');
|
|
@@ -2667,6 +2338,8 @@ export function StandaloneLayout(props) {
|
|
|
2667
2338
|
setPlanningNestedWorkstreamDetailId(null);
|
|
2668
2339
|
}, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
|
|
2669
2340
|
const closePlanningEditor = useCallback(() => {
|
|
2341
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2342
|
+
setIsSubmittingPlanningEditor(false);
|
|
2670
2343
|
setPlanningEditor(null);
|
|
2671
2344
|
setPlanningDraftTitle('');
|
|
2672
2345
|
setPlanningDraftDescription('');
|
|
@@ -2674,6 +2347,8 @@ export function StandaloneLayout(props) {
|
|
|
2674
2347
|
setPlanningDraftInitiativeId('');
|
|
2675
2348
|
}, []);
|
|
2676
2349
|
const closePlanningSecondaryEditor = useCallback(() => {
|
|
2350
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2351
|
+
setIsSubmittingPlanningEditor(false);
|
|
2677
2352
|
setPlanningSecondaryEditor(null);
|
|
2678
2353
|
setPlanningDraftTitle('');
|
|
2679
2354
|
setPlanningDraftDescription('');
|
|
@@ -2682,8 +2357,10 @@ export function StandaloneLayout(props) {
|
|
|
2682
2357
|
}, []);
|
|
2683
2358
|
const submitPlanningEditor = useCallback(async () => {
|
|
2684
2359
|
const activeEditor = planningSecondaryEditor || planningEditor;
|
|
2685
|
-
if (!activeEditor)
|
|
2360
|
+
if (!activeEditor || planningEditorSubmitInFlightRef.current)
|
|
2686
2361
|
return;
|
|
2362
|
+
planningEditorSubmitInFlightRef.current = true;
|
|
2363
|
+
setIsSubmittingPlanningEditor(true);
|
|
2687
2364
|
const isSecondaryEditor = Boolean(planningSecondaryEditor);
|
|
2688
2365
|
const ownerId = planningDraftOwner.trim() || null;
|
|
2689
2366
|
try {
|
|
@@ -2757,6 +2434,10 @@ export function StandaloneLayout(props) {
|
|
|
2757
2434
|
catch (error) {
|
|
2758
2435
|
pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
|
|
2759
2436
|
}
|
|
2437
|
+
finally {
|
|
2438
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2439
|
+
setIsSubmittingPlanningEditor(false);
|
|
2440
|
+
}
|
|
2760
2441
|
}, [
|
|
2761
2442
|
closePlanningEditor,
|
|
2762
2443
|
closePlanningSecondaryEditor,
|
|
@@ -2966,6 +2647,54 @@ export function StandaloneLayout(props) {
|
|
|
2966
2647
|
}
|
|
2967
2648
|
await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
|
|
2968
2649
|
}, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
|
|
2650
|
+
const getPlanningContextAttachmentDraft = useCallback((entityType, entityId) => {
|
|
2651
|
+
const key = `${entityType}:${entityId}`;
|
|
2652
|
+
const draft = planningContextAttachmentDraftsRef.current.get(key);
|
|
2653
|
+
if (draft)
|
|
2654
|
+
return draft;
|
|
2655
|
+
const entity = entityType === 'initiative'
|
|
2656
|
+
? props.initiatives.find((initiative) => initiative.id === entityId)
|
|
2657
|
+
: props.workstreams.find((workstream) => workstream.id === entityId);
|
|
2658
|
+
return Array.isArray(entity?.attachments) ? entity.attachments : [];
|
|
2659
|
+
}, [props.initiatives, props.workstreams]);
|
|
2660
|
+
const updatePlanningContextAttachments = useCallback((entityType, entityId, nextAttachments) => {
|
|
2661
|
+
const key = `${entityType}:${entityId}`;
|
|
2662
|
+
planningContextAttachmentDraftsRef.current.set(key, nextAttachments);
|
|
2663
|
+
const previousUpdate = planningContextAttachmentUpdateQueueRef.current.get(key) || Promise.resolve();
|
|
2664
|
+
const nextUpdate = previousUpdate
|
|
2665
|
+
.catch(() => undefined)
|
|
2666
|
+
.then(() => (entityType === 'initiative'
|
|
2667
|
+
? props.updateInitiative(entityId, { attachments: nextAttachments })
|
|
2668
|
+
: props.updateWorkstream(entityId, { attachments: nextAttachments })));
|
|
2669
|
+
planningContextAttachmentUpdateQueueRef.current.set(key, nextUpdate);
|
|
2670
|
+
void nextUpdate.catch((error) => {
|
|
2671
|
+
pushNotice(error instanceof Error ? error.message : 'Failed to update planning context.', 'error');
|
|
2672
|
+
}).finally(() => {
|
|
2673
|
+
if (planningContextAttachmentUpdateQueueRef.current.get(key) === nextUpdate) {
|
|
2674
|
+
planningContextAttachmentUpdateQueueRef.current.delete(key);
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
}, [props, pushNotice]);
|
|
2678
|
+
const handleAddPlanningContextFile = useCallback((entityType, entityId, file) => {
|
|
2679
|
+
const current = getPlanningContextAttachmentDraft(entityType, entityId);
|
|
2680
|
+
updatePlanningContextAttachments(entityType, entityId, [...current, file]);
|
|
2681
|
+
}, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
|
|
2682
|
+
const handleRemovePlanningContextFile = useCallback((entityType, entityId, index) => {
|
|
2683
|
+
const current = getPlanningContextAttachmentDraft(entityType, entityId);
|
|
2684
|
+
updatePlanningContextAttachments(entityType, entityId, current.filter((_, i) => i !== index));
|
|
2685
|
+
}, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
|
|
2686
|
+
const handleUpdatePlanningContextCaption = useCallback((entityType, entityId, index, caption) => {
|
|
2687
|
+
const current = getPlanningContextAttachmentDraft(entityType, entityId);
|
|
2688
|
+
const nextAttachments = current.map((attachment, i) => {
|
|
2689
|
+
if (i !== index)
|
|
2690
|
+
return attachment;
|
|
2691
|
+
if (typeof attachment === 'string') {
|
|
2692
|
+
return { path: attachment, caption, timestamp: new Date().toISOString() };
|
|
2693
|
+
}
|
|
2694
|
+
return { ...attachment, caption };
|
|
2695
|
+
});
|
|
2696
|
+
updatePlanningContextAttachments(entityType, entityId, nextAttachments);
|
|
2697
|
+
}, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
|
|
2969
2698
|
const handleInitiativeFilterChange = useCallback((initiativeId) => {
|
|
2970
2699
|
setSelectedPlanningInitiativeId(initiativeId);
|
|
2971
2700
|
setSelectedPlanningWorkstreamId('');
|
|
@@ -3202,10 +2931,12 @@ export function StandaloneLayout(props) {
|
|
|
3202
2931
|
}, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
|
|
3203
2932
|
}, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
|
|
3204
2933
|
const planningDrawerPortalRef = useRef(null);
|
|
3205
|
-
const planningDrawerNode = (_jsx(PlanningDrawer, { open: planningDrawerOpen, leftOffset: WORKSPACE_TOOL_RAIL_WIDTH, initiatives: planningStructure.initiatives, standaloneWorkstreams: planningStructure.standaloneWorkstreams, activeInitiativeId: selectedPlanningInitiativeId, activeWorkstreamId: selectedPlanningWorkstreamId, expandedInitiativeIds: expandedPlanningInitiativeIds, detail: planningDrawerItem, editor: planningEditor, secondaryPane: planningSecondaryPane, assigneeOptions: assigneeOptions.map((option) => ({ value: String(option.value), label: option.label })), 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: () => {
|
|
2934
|
+
const planningDrawerNode = (_jsx(PlanningDrawer, { open: planningDrawerOpen, leftOffset: WORKSPACE_TOOL_RAIL_WIDTH, initiatives: planningStructure.initiatives, standaloneWorkstreams: planningStructure.standaloneWorkstreams, activeInitiativeId: selectedPlanningInitiativeId, activeWorkstreamId: selectedPlanningWorkstreamId, expandedInitiativeIds: expandedPlanningInitiativeIds, detail: planningDrawerItem, editor: planningEditor, secondaryPane: planningSecondaryPane, currentWorkspaceId: currentWorkspaceId, assigneeOptions: assigneeOptions.map((option) => ({ value: String(option.value), label: option.label })), 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: () => {
|
|
2935
|
+
planningEditorSubmitInFlightRef.current = false;
|
|
2936
|
+
setIsSubmittingPlanningEditor(false);
|
|
3206
2937
|
setPlanningSecondaryEditor(null);
|
|
3207
2938
|
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, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onArchiveInitiative: (initiativeId) => {
|
|
2939
|
+
}, 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, 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
2940
|
void props.archiveInitiative(initiativeId).then(() => {
|
|
3210
2941
|
pushNotice('Initiative archived', 'success');
|
|
3211
2942
|
}).catch((error) => {
|
|
@@ -3240,7 +2971,7 @@ export function StandaloneLayout(props) {
|
|
|
3240
2971
|
const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
|
|
3241
2972
|
? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
|
|
3242
2973
|
: 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, meta:
|
|
2974
|
+
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, 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
2975
|
marginLeft: '6px',
|
|
3245
2976
|
height: '24px',
|
|
3246
2977
|
width: '24px',
|
|
@@ -3252,10 +2983,25 @@ export function StandaloneLayout(props) {
|
|
|
3252
2983
|
display: 'inline-flex',
|
|
3253
2984
|
alignItems: 'center',
|
|
3254
2985
|
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
|
|
2986
|
+
}, 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: () => {
|
|
2987
|
+
if (hasActiveCommercialEntitlement) {
|
|
2988
|
+
void handlePlansBack();
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
closePlansScreen();
|
|
2992
|
+
}, 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: {
|
|
2993
|
+
position: 'relative',
|
|
2994
|
+
display: 'flex',
|
|
2995
|
+
flex: 1,
|
|
2996
|
+
minHeight: 0,
|
|
2997
|
+
overflowY: 'auto',
|
|
2998
|
+
overflowX: 'hidden',
|
|
2999
|
+
scrollbarGutter: 'stable',
|
|
3000
|
+
background: 'var(--surface-page)'
|
|
3001
|
+
}, 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, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
|
|
3256
3002
|
setRequestedDocPath(null);
|
|
3257
3003
|
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 }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
|
|
3004
|
+
}, 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, cloudAuthConfigured: cloudAuthConfigured, authSessionResolved: authSessionResolved, isAuthenticated: isAuthenticated, cloudAiProfileSeatUsage: accountProfileSummary?.aiProfileSeatUsage ?? null }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
|
|
3259
3005
|
position: 'relative',
|
|
3260
3006
|
opacity: props.loadingTasks ? 0.7 : 1,
|
|
3261
3007
|
transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
@@ -3271,7 +3017,7 @@ export function StandaloneLayout(props) {
|
|
|
3271
3017
|
inset: 0,
|
|
3272
3018
|
zIndex: 35,
|
|
3273
3019
|
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) => {
|
|
3020
|
+
} }), _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
3021
|
setScheduleSelectedDate(dateOnly);
|
|
3276
3022
|
const parsed = parseDateOnlyLocal(dateOnly);
|
|
3277
3023
|
if (parsed) {
|
|
@@ -3382,11 +3128,15 @@ export function StandaloneLayout(props) {
|
|
|
3382
3128
|
return;
|
|
3383
3129
|
}
|
|
3384
3130
|
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: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(ScheduleWarningModal, { prompt: scheduleWarningPrompt, theme: currentTheme, onClose: cancelScheduleWarning, onConfirm: confirmScheduleWarning }), _jsx(HelpModal, { isOpen: showHelpModal, theme: currentTheme, onClose: () => setShowHelpModal(false), onOpenSettings: () => {
|
|
3131
|
+
}, 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: settingsModel, 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
3132
|
setShowHelpModal(false);
|
|
3387
3133
|
setActiveTab('settings');
|
|
3388
3134
|
setSettingsSection('general');
|
|
3389
|
-
} }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial,
|
|
3135
|
+
} }), _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: () => {
|
|
3136
|
+
setProfileSaveError(null);
|
|
3137
|
+
setProfileSaveNotice(null);
|
|
3138
|
+
setShowAvatarPhotoManager(true);
|
|
3139
|
+
} }), _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
3140
|
setShowAccountHub(false);
|
|
3391
3141
|
void openTeamManagementModal();
|
|
3392
3142
|
setTeamManagementTab('audit');
|
|
@@ -3442,7 +3192,7 @@ export function StandaloneLayout(props) {
|
|
|
3442
3192
|
headers: teamAdminHeaders(),
|
|
3443
3193
|
credentials: 'include'
|
|
3444
3194
|
}));
|
|
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:
|
|
3195
|
+
}, 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
3196
|
void handleWorkspaceSyncToggle(enabled);
|
|
3447
3197
|
}, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
|
|
3448
3198
|
openSharedLoginRoute('login');
|