@taskforcehq/taskforce 0.3.305 → 0.3.307
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/Taskforce.module.css +603 -107
- package/dist/TaskforceCore.js +216 -43
- package/dist/TaskforceCore.test.js +611 -28
- package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/assets/fonts/Noir_medium.woff2 +0 -0
- package/dist/assets/fonts/Noir_regular.woff2 +0 -0
- package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/compat/workspaceSyncCompat.js +46 -2
- package/dist/compat/workspaceSyncCompat.test.js +38 -1
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +561 -0
- package/dist/components/features/AgentsModule.d.ts +14 -1
- package/dist/components/features/AgentsModule.js +366 -18
- package/dist/components/features/AgentsModule.test.js +507 -8
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +7 -1
- package/dist/components/features/DocumentViewer.js +62 -4
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +51 -1
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +24 -29
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +61 -116
- package/dist/components/task/TaskForm.test.js +110 -8
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +94 -21
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +78 -14
- package/dist/components/views/PlanComparisonPage.test.js +257 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +529 -592
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +25 -5
- package/dist/components/views/panels/PlanningDrawer.js +270 -55
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +529 -0
- package/dist/components/views/planningScope.d.ts +20 -0
- package/dist/components/views/planningScope.js +25 -0
- package/dist/components/views/planningScope.test.d.ts +1 -0
- package/dist/components/views/planningScope.test.js +60 -0
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
- package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +46 -2
- package/dist/core/AiProfileService.js +1073 -82
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +1249 -44
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +110 -23
- package/dist/core/AuthIdentityService.d.ts +107 -0
- package/dist/core/AuthIdentityService.js +284 -0
- package/dist/core/AuthTokenService.d.ts +4 -0
- package/dist/core/AuthTokenService.js +34 -6
- package/dist/core/AuthTokenService.test.js +3 -3
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +127 -6
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanEntitlementService.d.ts +23 -2
- package/dist/core/PlanEntitlementService.js +343 -57
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +35 -2
- package/dist/core/PlanningEntities.test.js +418 -4
- package/dist/core/SignupMonetizationSettings.test.js +199 -27
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +42 -42
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +415 -40
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +1543 -266
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
- package/dist/core/WorkspaceLifecycleService.js +0 -5
- package/dist/core/WorkspacePermissions.test.js +25 -1
- package/dist/core/WorkspacePlanMode.test.js +117 -16
- package/dist/core/shared.d.ts +1 -1
- package/dist/core/shared.js +5 -1
- package/dist/core/types.d.ts +75 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +28 -2
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/bootstrap.js +2 -0
- package/dist/hooks/sync/controlPlane.d.ts +37 -0
- package/dist/hooks/sync/controlPlane.js +78 -0
- package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
- package/dist/hooks/sync/controlPlane.test.js +121 -0
- package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
- package/dist/hooks/sync/lifecyclePolicy.js +93 -0
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
- package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +13 -4
- package/dist/hooks/sync/orchestratorShared.js +129 -48
- package/dist/hooks/sync/orchestratorShared.test.js +50 -1
- package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
- package/dist/hooks/sync/pullLifecycle.js +24 -0
- package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
- package/dist/hooks/sync/pullLifecycle.test.js +80 -0
- package/dist/hooks/sync/recovery.d.ts +16 -2
- package/dist/hooks/sync/recovery.js +179 -53
- package/dist/hooks/sync/recovery.test.d.ts +1 -0
- package/dist/hooks/sync/recovery.test.js +292 -0
- package/dist/hooks/sync/transfers.d.ts +16 -3
- package/dist/hooks/sync/transfers.js +210 -66
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +928 -0
- package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
- package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
- package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
- package/dist/hooks/sync/useSyncStatusActions.js +88 -0
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
- package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
- package/dist/hooks/sync/useSyncStatusControls.js +60 -0
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
- package/dist/hooks/useSyncOrchestrator.js +409 -183
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskforce.d.ts +145 -52
- package/dist/hooks/useTaskforce.js +592 -253
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +246 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2468 -201
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +13 -2
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +328 -40
- package/dist/localization/locales/en-US.d.ts +0 -1
- package/dist/localization/locales/en-US.js +0 -1
- package/dist/localization/locales/es-419.js +0 -1
- package/dist/localization/locales/pt-BR.js +0 -1
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +116 -10
- package/dist/mcp/runtime.d.ts +3 -0
- package/dist/mcp/runtime.js +1099 -185
- package/dist/mcp/runtime.test.js +1491 -140
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +192 -61
- package/dist/migrations/taskSchemaMigrations.test.js +15 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/dist/resources/templates/workflows/collaborate.yaml +1 -1
- package/dist/resources/templates/workflows/evaluate.yaml +2 -2
- package/dist/resources/templates/workflows/plan.yaml +1 -1
- package/dist/resources/templates/workflows/review.yaml +2 -2
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +69 -10
- package/dist/server/auth/providers/apple.d.ts +33 -0
- package/dist/server/auth/providers/apple.js +82 -0
- package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
- package/dist/server/auth/providers/appleClientSecret.js +65 -0
- package/dist/server/auth/providers/github.d.ts +26 -0
- package/dist/server/auth/providers/github.js +86 -0
- package/dist/server/auth/providers/google.d.ts +21 -0
- package/dist/server/auth/providers/google.js +56 -0
- package/dist/server/auth/providers/types.d.ts +21 -0
- package/dist/server/auth/providers/types.js +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.js +6 -3
- package/dist/server/documentReviewRoutes.test.js +36 -3
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +124 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +111 -3
- package/dist/server/routes/admin.d.ts +3 -0
- package/dist/server/routes/admin.js +459 -28
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +26 -2
- package/dist/server/routes/auth.js +1170 -71
- package/dist/server/routes/authSupport.d.ts +5 -1
- package/dist/server/routes/authSupport.js +13 -2
- package/dist/server/routes/billing.js +372 -35
- package/dist/server/routes/billing.test.js +595 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +182 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -14
- package/dist/server/routes/sync.integration.test.js +658 -39
- package/dist/server/routes/sync.js +74 -608
- package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
- package/dist/server/routes/syncAuxRoutes.js +91 -0
- package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
- package/dist/server/routes/syncAuxRoutes.test.js +158 -0
- package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
- package/dist/server/routes/syncPullApplyRoutes.js +204 -0
- package/dist/server/routes/syncPushRoutes.d.ts +24 -0
- package/dist/server/routes/syncPushRoutes.js +212 -0
- package/dist/server/routes/syncRouteGuards.d.ts +36 -0
- package/dist/server/routes/syncRouteGuards.js +67 -0
- package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
- package/dist/server/routes/syncRouteGuards.test.js +94 -0
- package/dist/server/routes/syncRouteTypes.d.ts +13 -0
- package/dist/server/routes/syncRouteTypes.js +1 -0
- package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
- package/dist/server/routes/syncSnapshotRoutes.js +182 -0
- package/dist/server/routes/tasks.js +55 -10
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +147 -11
- package/dist/server/routes.test.js +2704 -183
- package/dist/services/workflowExportSnapshots.test.js +2 -0
- package/dist/shared/aiProfileSeatScope.d.ts +5 -0
- package/dist/shared/aiProfileSeatScope.js +21 -0
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/shared/passwordPolicy.d.ts +13 -0
- package/dist/shared/passwordPolicy.js +84 -0
- package/dist/shared/passwordPolicy.test.d.ts +1 -0
- package/dist/shared/passwordPolicy.test.js +34 -0
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/storage/workspaceAssetStore.d.ts +15 -3
- package/dist/storage/workspaceAssetStore.js +132 -44
- package/dist/storage/workspaceAssetStore.test.js +93 -6
- package/dist/styles/fonts.css +70 -0
- package/dist/sync/collaborationSyncPayload.d.ts +6 -1
- package/dist/sync/collaborationSyncPayload.js +19 -11
- package/dist/sync/collaborationSyncPayload.test.js +14 -1
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +9 -9
- package/dist/sync/contentSyncState.js +248 -107
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +11 -4
- package/dist/sync/syncApplyHandlers.js +115 -42
- package/dist/sync/syncApplyHandlers.test.js +251 -4
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +46 -0
- package/dist/sync/syncResourceAdapters.js +79 -0
- package/dist/sync/syncService.d.ts +6 -0
- package/dist/sync/syncService.js +65 -1
- package/dist/sync/syncService.test.js +96 -3
- package/dist/sync/taskSyncChangeKey.d.ts +2 -0
- package/dist/sync/taskSyncChangeKey.js +143 -0
- package/dist/sync/taskSyncPayload.js +3 -8
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +132 -72
- package/dist/sync/workspacePullFeed.test.js +92 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +73 -6
- package/dist/sync/workspaceSyncModel.js +342 -73
- package/dist/sync/workspaceSyncModel.test.js +559 -14
- package/dist/sync/workspaceSyncState.d.ts +17 -0
- package/dist/sync/workspaceSyncState.js +2 -1
- package/dist/sync/workspaceSyncSurface.js +3 -8
- package/dist/types.d.ts +50 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
- package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
- package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/ui/fonts/Noir_medium.otf +0 -0
- package/dist/ui/fonts/Noir_medium.woff2 +0 -0
- package/dist/ui/fonts/Noir_regular.otf +0 -0
- package/dist/ui/fonts/Noir_regular.woff2 +0 -0
- package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +8 -0
- package/dist/utils/assignees.js +10 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/avatarUpload.d.ts +27 -0
- package/dist/utils/avatarUpload.js +132 -0
- package/dist/utils/avatarUpload.test.d.ts +1 -0
- package/dist/utils/avatarUpload.test.js +40 -0
- package/dist/utils/bootstrapTrace.d.ts +7 -0
- package/dist/utils/bootstrapTrace.js +29 -0
- package/dist/utils/commercialLifecyclePresentation.js +3 -0
- package/dist/utils/contextAssetEvents.d.ts +2 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/syncEventDetails.d.ts +3 -0
- package/dist/utils/syncEventDetails.js +64 -0
- package/dist/utils/syncEventDetails.test.d.ts +1 -0
- package/dist/utils/syncEventDetails.test.js +23 -0
- package/dist/utils/syncEventPresentation.d.ts +15 -0
- package/dist/utils/syncEventPresentation.js +129 -0
- package/dist/utils/syncEventPresentation.test.d.ts +1 -0
- package/dist/utils/syncEventPresentation.test.js +64 -0
- package/dist/utils/syncStatusPresentation.d.ts +72 -0
- package/dist/utils/syncStatusPresentation.js +217 -0
- package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
- package/dist/utils/syncStatusPresentation.test.js +164 -0
- package/dist/utils/taskActivity.d.ts +9 -1
- package/dist/utils/taskActivity.js +290 -22
- package/dist/utils/taskActivity.test.js +141 -8
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
- package/dist/utils/workspaceSyncPresentation.js +68 -2
- package/dist/utils/workspaceSyncPresentation.test.js +36 -1
- package/package.json +3 -2
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/run-e2e-realtime.sh +17 -3
- package/scripts/run-smoke.sh +17 -5
- package/scripts/run-soak.sh +17 -5
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/src/resources/templates/workflows/collaborate.yaml +1 -1
- package/src/resources/templates/workflows/evaluate.yaml +2 -2
- package/src/resources/templates/workflows/plan.yaml +1 -1
- package/src/resources/templates/workflows/review.yaml +2 -2
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BG6P_GVW.js +0 -3
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +0 -9
- package/dist/ui/assets/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { ArrowDown, ArrowLeft, ArrowUpLeft, ArrowUp, Check, ClipboardPaste, Copy, FolderOpen, Hand, HelpCircle, Loader2, MessageSquareCode, MessageSquareText, MousePointer2, Pin, Plus, RotateCcw, Save, SquareMousePointer, Trash2, ZoomIn, ZoomOut, } from 'lucide-react';
|
|
3
|
+
import { ArrowDown, ArrowLeft, ArrowUpLeft, ArrowUp, Check, ChevronLeft, ClipboardPaste, Copy, FolderOpen, Hand, HelpCircle, Loader2, MessageSquareCode, MessageSquareText, MousePointer2, Pin, Plus, RotateCcw, Save, Search, SquareMousePointer, Trash2, ZoomIn, ZoomOut, } from 'lucide-react';
|
|
4
4
|
import sharedStyles from '../../Taskforce.module.css';
|
|
5
5
|
import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
|
|
6
6
|
import { logBootstrapDebug } from '../../hooks/workspace/workspaceLocalState';
|
|
@@ -132,6 +132,33 @@ function formatTimestamp(value) {
|
|
|
132
132
|
return 'Unsaved';
|
|
133
133
|
return parsed.toLocaleString();
|
|
134
134
|
}
|
|
135
|
+
function formatFileSize(bytes) {
|
|
136
|
+
const normalized = typeof bytes === 'number' && Number.isFinite(bytes) ? Math.max(0, bytes) : 0;
|
|
137
|
+
if (normalized < 1024)
|
|
138
|
+
return `${normalized}B`;
|
|
139
|
+
if (normalized < 1024 * 1024)
|
|
140
|
+
return `${(normalized / 1024).toFixed(1)}KB`;
|
|
141
|
+
return `${(normalized / (1024 * 1024)).toFixed(1)}MB`;
|
|
142
|
+
}
|
|
143
|
+
function formatRelativeDate(isoDate) {
|
|
144
|
+
if (!isoDate)
|
|
145
|
+
return '';
|
|
146
|
+
const parsed = new Date(isoDate);
|
|
147
|
+
if (Number.isNaN(parsed.getTime()))
|
|
148
|
+
return '';
|
|
149
|
+
const now = new Date();
|
|
150
|
+
const diffMs = now.getTime() - parsed.getTime();
|
|
151
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
152
|
+
if (diffDays <= 0)
|
|
153
|
+
return 'Today';
|
|
154
|
+
if (diffDays === 1)
|
|
155
|
+
return 'Yesterday';
|
|
156
|
+
if (diffDays < 7)
|
|
157
|
+
return `${diffDays}d ago`;
|
|
158
|
+
if (diffDays < 30)
|
|
159
|
+
return `${Math.floor(diffDays / 7)}w ago`;
|
|
160
|
+
return parsed.toLocaleDateString();
|
|
161
|
+
}
|
|
135
162
|
function getSessionCreatorLabel(session) {
|
|
136
163
|
const createdByLabel = String(session.createdByActor?.label || '').trim();
|
|
137
164
|
if (createdByLabel)
|
|
@@ -371,7 +398,7 @@ function buildDraftPayloadFromSession(session, fallbackTitle) {
|
|
|
371
398
|
annotations: session?.annotations || [],
|
|
372
399
|
});
|
|
373
400
|
}
|
|
374
|
-
export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', workspaceId = 'default', sessionLoadReady = true, requestedTarget = null, requestedSessionId = null, requestedOpenVersion = 0, resolveTaskReferenceLabel, resolveImageReferenceLabel, onRequestedTargetHandled, onOpenTarget, onContextChange, onBackToTask, }) {
|
|
401
|
+
export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', workspaceId = 'default', sessionLoadReady = true, requestedTarget = null, requestedSessionId = null, requestedOpenVersion = 0, imageTrayOpen, onCloseImageTray, resolveTaskReferenceLabel, resolveImageReferenceLabel, onRequestedTargetHandled, onOpenTarget, onContextChange, onBackToTask, }) {
|
|
375
402
|
const resolvedApiBase = runtimeMode === 'cloud'
|
|
376
403
|
? (cloudAuthBaseUrl || apiBaseUrl || '')
|
|
377
404
|
: '';
|
|
@@ -403,6 +430,10 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
403
430
|
const [openImageModalOpen, setOpenImageModalOpen] = useState(false);
|
|
404
431
|
const [openImageReference, setOpenImageReference] = useState('');
|
|
405
432
|
const [openingImage, setOpeningImage] = useState(false);
|
|
433
|
+
const [imageTrayItems, setImageTrayItems] = useState([]);
|
|
434
|
+
const [imageTraySearchQuery, setImageTraySearchQuery] = useState('');
|
|
435
|
+
const [imageTrayLoading, setImageTrayLoading] = useState(false);
|
|
436
|
+
const [imageTrayError, setImageTrayError] = useState(null);
|
|
406
437
|
const [imageLoading, setImageLoading] = useState(false);
|
|
407
438
|
const [imageLoadError, setImageLoadError] = useState(false);
|
|
408
439
|
const [zoomLevel, setZoomLevel] = useState(1);
|
|
@@ -486,6 +517,22 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
486
517
|
hitRadius: 11,
|
|
487
518
|
}), []);
|
|
488
519
|
const arrowOverlayViewBox = useMemo(() => `0 0 ${Math.max(canvasMediaSize.width, 1)} ${Math.max(canvasMediaSize.height, 1)}`, [canvasMediaSize.height, canvasMediaSize.width]);
|
|
520
|
+
const showImageTray = typeof imageTrayOpen === 'boolean';
|
|
521
|
+
const filteredImageTrayItems = useMemo(() => {
|
|
522
|
+
const query = imageTraySearchQuery.trim().toLowerCase();
|
|
523
|
+
if (!query)
|
|
524
|
+
return imageTrayItems;
|
|
525
|
+
return imageTrayItems.filter((item) => {
|
|
526
|
+
const haystack = [
|
|
527
|
+
item.displayName,
|
|
528
|
+
item.originalFilename,
|
|
529
|
+
item.imageReferenceLabel,
|
|
530
|
+
item.taskReferenceLabel,
|
|
531
|
+
item.assetId,
|
|
532
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
533
|
+
return haystack.includes(query);
|
|
534
|
+
});
|
|
535
|
+
}, [imageTrayItems, imageTraySearchQuery]);
|
|
489
536
|
const canPan = useMemo(() => {
|
|
490
537
|
const scroller = canvasScrollerRef.current;
|
|
491
538
|
if (!scroller)
|
|
@@ -889,6 +936,26 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
889
936
|
}
|
|
890
937
|
}
|
|
891
938
|
}, [clearPendingAutoSave, createSessionForTarget, requestHeaders, resolvedApiBase, syncDraftFromSession, workspaceId]);
|
|
939
|
+
const fetchImageTrayItems = useCallback(async () => {
|
|
940
|
+
setImageTrayLoading(true);
|
|
941
|
+
setImageTrayError(null);
|
|
942
|
+
try {
|
|
943
|
+
const params = new URLSearchParams();
|
|
944
|
+
params.set('workspaceId', String(workspaceId || 'default').trim() || 'default');
|
|
945
|
+
const res = await fetchTaskforceApi(`/api/taskforce/annotated-attachments/images?${params.toString()}`, { credentials: 'include', headers: requestHeaders, cache: 'no-store' }, resolvedApiBase);
|
|
946
|
+
const data = await res.json().catch(() => ({}));
|
|
947
|
+
if (!res.ok) {
|
|
948
|
+
throw new Error(String(data?.error || 'Failed to load images.'));
|
|
949
|
+
}
|
|
950
|
+
setImageTrayItems(Array.isArray(data?.images) ? data.images : []);
|
|
951
|
+
}
|
|
952
|
+
catch (requestError) {
|
|
953
|
+
setImageTrayError(requestError instanceof Error ? requestError.message : 'Failed to load images.');
|
|
954
|
+
}
|
|
955
|
+
finally {
|
|
956
|
+
setImageTrayLoading(false);
|
|
957
|
+
}
|
|
958
|
+
}, [requestHeaders, resolvedApiBase, workspaceId]);
|
|
892
959
|
const pasteImageFromClipboard = useCallback(async () => {
|
|
893
960
|
if (typeof navigator === 'undefined' || !navigator.clipboard || typeof navigator.clipboard.read !== 'function') {
|
|
894
961
|
setError('Clipboard image paste is not supported in this environment.');
|
|
@@ -1016,6 +1083,33 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1016
1083
|
setOpeningImage(false);
|
|
1017
1084
|
}
|
|
1018
1085
|
}, [isTargetControlled, loadSessions, onOpenTarget, openImageReference, requestHeaders, workspaceId]);
|
|
1086
|
+
const openImageFromTray = useCallback(async (item) => {
|
|
1087
|
+
if (!item.assetId || !item.path)
|
|
1088
|
+
return;
|
|
1089
|
+
const flushed = await flushPendingDraft('session-switch');
|
|
1090
|
+
if (!flushed)
|
|
1091
|
+
return;
|
|
1092
|
+
const target = {
|
|
1093
|
+
assetId: item.assetId,
|
|
1094
|
+
path: item.path,
|
|
1095
|
+
displayName: String(item.displayName || item.originalFilename || 'Image attachment').trim() || 'Image attachment',
|
|
1096
|
+
...(item.taskId ? { taskId: item.taskId } : {}),
|
|
1097
|
+
...(item.taskReferenceLabel ? { taskReferenceLabel: item.taskReferenceLabel } : {}),
|
|
1098
|
+
...(item.imageReferenceLabel ? { imageReferenceLabel: item.imageReferenceLabel } : {}),
|
|
1099
|
+
};
|
|
1100
|
+
pendingRequestedSessionIdRef.current = null;
|
|
1101
|
+
setError(null);
|
|
1102
|
+
if (isTargetControlled) {
|
|
1103
|
+
lastLoadedTargetKeyRef.current = '';
|
|
1104
|
+
onOpenTarget?.(target, { sessionId: null });
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
setUncontrolledTarget(target);
|
|
1108
|
+
lastLoadedTargetKeyRef.current = getAnnotatedTargetKey(target);
|
|
1109
|
+
resetForTargetLoad(target);
|
|
1110
|
+
void loadSessions(target, { autoCreateIfEmpty: true });
|
|
1111
|
+
}
|
|
1112
|
+
}, [flushPendingDraft, isTargetControlled, loadSessions, onOpenTarget, resetForTargetLoad]);
|
|
1019
1113
|
const applySessionLocally = useCallback((session) => {
|
|
1020
1114
|
setSessions((current) => {
|
|
1021
1115
|
const existingIndex = current.findIndex((item) => item.id === session.id);
|
|
@@ -1207,6 +1301,11 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1207
1301
|
setLastSaveError(null);
|
|
1208
1302
|
setSaveState('saved');
|
|
1209
1303
|
}, [currentDraftSnapshot, saveState, selectedSessionId]);
|
|
1304
|
+
useEffect(() => {
|
|
1305
|
+
if (!showImageTray || !imageTrayOpen)
|
|
1306
|
+
return;
|
|
1307
|
+
void fetchImageTrayItems();
|
|
1308
|
+
}, [fetchImageTrayItems, imageTrayOpen, showImageTray]);
|
|
1210
1309
|
useEffect(() => {
|
|
1211
1310
|
if (typeof window === 'undefined')
|
|
1212
1311
|
return undefined;
|
|
@@ -1802,7 +1901,22 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1802
1901
|
event.stopPropagation();
|
|
1803
1902
|
}, [panMode, readPoint, toolMode]);
|
|
1804
1903
|
const activeSessionTimestamp = selectedSession?.updatedAt ? formatTimestamp(selectedSession.updatedAt) : 'Not saved yet';
|
|
1805
|
-
return (_jsxs("div", { className: styles.shell, children: [_jsxs("
|
|
1904
|
+
return (_jsxs("div", { className: `${styles.shell} ${showImageTray ? styles.shellWithImageTray : ''} ${showImageTray && imageTrayOpen ? styles.shellImageTrayOpen : ''}`.trim(), children: [showImageTray ? (_jsxs("aside", { className: `${styles.imageTrayPanel} ${imageTrayOpen ? styles.imageTrayPanelOpen : ''}`.trim(), "aria-label": "Image tray", "aria-hidden": !imageTrayOpen, children: [_jsxs("div", { className: styles.imageTrayHeader, children: [_jsxs("span", { className: styles.imageTrayTitle, children: [_jsx(FolderOpen, { size: 14 }), "Images"] }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: onCloseImageTray, title: "Collapse image tray", "aria-label": "Collapse image tray", children: _jsx(ChevronLeft, { size: 16 }) })] }), _jsxs("div", { className: styles.imageTraySearch, children: [_jsx(Search, { size: 12, className: styles.imageTraySearchIcon }), _jsx("input", { type: "text", placeholder: "Search images...", value: imageTraySearchQuery, onChange: (event) => setImageTraySearchQuery(event.target.value), className: styles.imageTraySearchInput })] }), _jsx("div", { className: `tf-scrollbar ${styles.imageTrayList}`, children: imageTrayLoading ? (_jsx("div", { className: styles.imageTrayState, children: "Loading images..." })) : imageTrayError ? (_jsx("div", { className: styles.imageTrayStateError, children: imageTrayError })) : filteredImageTrayItems.length === 0 ? (_jsx("div", { className: styles.imageTrayState, children: imageTraySearchQuery.trim() ? 'No images match your search.' : 'No images found.' })) : filteredImageTrayItems.map((item) => {
|
|
1905
|
+
const isSelected = activeTarget?.assetId === item.assetId;
|
|
1906
|
+
const attachmentCount = typeof item.attachmentCount === 'number' && Number.isFinite(item.attachmentCount)
|
|
1907
|
+
? Math.max(0, item.attachmentCount)
|
|
1908
|
+
: 0;
|
|
1909
|
+
const sessionCount = typeof item.sessionCount === 'number' && Number.isFinite(item.sessionCount)
|
|
1910
|
+
? Math.max(0, item.sessionCount)
|
|
1911
|
+
: 0;
|
|
1912
|
+
const linkedLabel = attachmentCount > 1
|
|
1913
|
+
? `${attachmentCount} tasks linked`
|
|
1914
|
+
: attachmentCount === 1
|
|
1915
|
+
? item.taskReferenceLabel || '1 task linked'
|
|
1916
|
+
: 'Unattached';
|
|
1917
|
+
const imageName = String(item.displayName || item.originalFilename || 'Image attachment').trim() || 'Image attachment';
|
|
1918
|
+
return (_jsxs("button", { type: "button", className: `${styles.imageTrayItem} ${isSelected ? styles.imageTrayItemActive : ''}`.trim(), onClick: () => void openImageFromTray(item), title: imageName, children: [_jsx("span", { className: styles.imageTrayThumb, children: _jsx("img", { src: item.path, alt: "", loading: "lazy" }) }), _jsxs("span", { className: styles.imageTrayItemBody, children: [_jsx("span", { className: styles.imageTrayItemTitle, children: imageName }), _jsx("span", { className: styles.imageTrayItemTask, children: linkedLabel }), _jsxs("span", { className: styles.imageTrayItemMeta, children: [_jsxs("span", { className: styles.imageTrayItemMetaDetails, children: [formatFileSize(item.sizeBytes), item.updatedAt ? _jsxs(_Fragment, { children: [" \u00B7 ", formatRelativeDate(item.updatedAt)] }) : null, sessionCount > 0 ? _jsxs(_Fragment, { children: [" \u00B7 ", sessionCount, " session", sessionCount === 1 ? '' : 's'] }) : null] }), item.imageReferenceLabel ? (_jsx("span", { className: styles.imageTrayItemReference, children: item.imageReferenceLabel })) : null] })] })] }, item.assetId));
|
|
1919
|
+
}) })] })) : null, _jsxs("section", { className: `tf-surface-panel ${styles.panel} ${styles.sessionPanel}`, children: [_jsx("div", { className: styles.sessionContextBar, children: activeTarget?.taskId ? (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.sessionContextLeft, children: onBackToTask ? (_jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.ghostBtn} ${styles.backToTaskBtn}`, onClick: () => {
|
|
1806
1920
|
void flushPendingDraft('back-to-task').then((saved) => {
|
|
1807
1921
|
if (!saved)
|
|
1808
1922
|
return;
|
|
@@ -320,6 +320,58 @@ describe('AnnotatedAttachmentWorkspaceShell', () => {
|
|
|
320
320
|
expect(screen.queryByAltText('Homepage shot')).not.toBeInTheDocument();
|
|
321
321
|
});
|
|
322
322
|
});
|
|
323
|
+
it('loads images from the image tray and opens the selected image', async () => {
|
|
324
|
+
const user = userEvent.setup();
|
|
325
|
+
fetchTaskforceApi
|
|
326
|
+
.mockResolvedValueOnce({
|
|
327
|
+
ok: true,
|
|
328
|
+
json: async () => ({
|
|
329
|
+
images: [
|
|
330
|
+
{
|
|
331
|
+
assetId: 'asset-image-24',
|
|
332
|
+
imageReferenceLabel: 'I-24',
|
|
333
|
+
path: '/api/taskforce/context/workspaces%2Fworkspace-1%2Fassets%2Fimages%2Fasset-image-24.png',
|
|
334
|
+
displayName: 'Reference image',
|
|
335
|
+
originalFilename: 'reference.png',
|
|
336
|
+
sizeBytes: 2048,
|
|
337
|
+
updatedAt: '2026-03-25T12:00:00.000Z',
|
|
338
|
+
attachmentCount: 0,
|
|
339
|
+
sessionCount: 0,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
}),
|
|
343
|
+
})
|
|
344
|
+
.mockResolvedValueOnce({
|
|
345
|
+
ok: true,
|
|
346
|
+
json: async () => ({ sessions: [] }),
|
|
347
|
+
})
|
|
348
|
+
.mockResolvedValueOnce({
|
|
349
|
+
ok: true,
|
|
350
|
+
json: async () => ({
|
|
351
|
+
session: buildSessionResponse({
|
|
352
|
+
id: 'session-ref-1',
|
|
353
|
+
taskId: '',
|
|
354
|
+
baseImageAssetId: 'asset-image-24',
|
|
355
|
+
title: 'Reference image review',
|
|
356
|
+
}),
|
|
357
|
+
}),
|
|
358
|
+
});
|
|
359
|
+
function ControlledImageTrayHarness() {
|
|
360
|
+
const [target, setTarget] = React.useState(null);
|
|
361
|
+
const [sessionId, setSessionId] = React.useState(null);
|
|
362
|
+
return (_jsx(AnnotatedAttachmentWorkspaceShell, { workspaceId: "workspace-1", imageTrayOpen: true, requestedTarget: target, requestedSessionId: sessionId, onOpenTarget: (nextTarget, options) => {
|
|
363
|
+
setTarget(nextTarget);
|
|
364
|
+
setSessionId(options?.sessionId ?? null);
|
|
365
|
+
} }));
|
|
366
|
+
}
|
|
367
|
+
render(_jsx(ControlledImageTrayHarness, {}));
|
|
368
|
+
expect(await screen.findByRole('button', { name: /reference image/i })).toBeInTheDocument();
|
|
369
|
+
await user.click(screen.getByRole('button', { name: /reference image/i }));
|
|
370
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(1, '/api/taskforce/annotated-attachments/images?workspaceId=workspace-1', expect.objectContaining({ cache: 'no-store' }), ''));
|
|
371
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(2, '/api/taskforce/annotated-attachments/sessions?workspaceId=workspace-1&imageAssetId=asset-image-24', expect.any(Object), ''));
|
|
372
|
+
expect(await screen.findByAltText('Reference image')).toBeInTheDocument();
|
|
373
|
+
expect(await findSessionCard('Reference image review')).toBeInTheDocument();
|
|
374
|
+
});
|
|
323
375
|
it('does not revert to the previous image while opening a pasted image in controlled mode', async () => {
|
|
324
376
|
const user = userEvent.setup();
|
|
325
377
|
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
|
@@ -11,8 +11,8 @@ interface Props {
|
|
|
11
11
|
}>;
|
|
12
12
|
onSearchChange: (q: string) => void;
|
|
13
13
|
onSelect: (doc: DocMeta) => void;
|
|
14
|
-
|
|
14
|
+
onCloseTray?: () => void;
|
|
15
15
|
hasActiveFilters: boolean;
|
|
16
16
|
}
|
|
17
|
-
export declare function DocumentIndex({ documents, selectedDocId, loading, error, searchQuery, taskById, onSearchChange, onSelect,
|
|
17
|
+
export declare function DocumentIndex({ documents, selectedDocId, loading, error, searchQuery, taskById, onSearchChange, onSelect, onCloseTray, hasActiveFilters, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
18
18
|
export {};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Search,
|
|
3
|
-
import styles from '../../Taskforce.module.css';
|
|
2
|
+
import { Search, FileText, ClipboardList, Eye, BookOpen, File, ChevronLeft } from 'lucide-react';
|
|
4
3
|
import indexStyles from './DocumentIndex.module.css';
|
|
5
4
|
import { getEffectiveAttachmentState } from './documentWorkspaceModel';
|
|
6
5
|
import { getReadableDocumentName } from '../../utils/documentNames';
|
|
@@ -42,8 +41,8 @@ function handleItemKeyDown(event, onSelect) {
|
|
|
42
41
|
event.preventDefault();
|
|
43
42
|
onSelect();
|
|
44
43
|
}
|
|
45
|
-
export function DocumentIndex({ documents, selectedDocId, loading, error, searchQuery, taskById, onSearchChange, onSelect,
|
|
46
|
-
return (_jsxs("div", { className: indexStyles.index, children: [_jsxs("div", { className: indexStyles.header, children: [_jsxs("span", { className: indexStyles.title, children: [_jsx(FileText, { size: 14 }), "Documents"] }), _jsx("button", { className:
|
|
44
|
+
export function DocumentIndex({ documents, selectedDocId, loading, error, searchQuery, taskById, onSearchChange, onSelect, onCloseTray, hasActiveFilters, }) {
|
|
45
|
+
return (_jsxs("div", { className: indexStyles.index, children: [_jsxs("div", { className: indexStyles.header, children: [_jsxs("span", { className: indexStyles.title, children: [_jsx(FileText, { size: 14 }), "Documents"] }), onCloseTray ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCloseTray, title: "Collapse document tray", "aria-label": "Collapse document tray", children: _jsx(ChevronLeft, { size: 16 }) })) : null] }), _jsxs("div", { className: indexStyles.search, children: [_jsx(Search, { size: 12, className: indexStyles.searchIcon }), _jsx("input", { type: "text", placeholder: "Search documents\u2026", value: searchQuery, onChange: e => onSearchChange(e.target.value), className: indexStyles.searchInput })] }), _jsxs("div", { className: `${indexStyles.list} tf-scrollbar`, children: [loading && (_jsx("div", { className: indexStyles.state, children: "Loading documents\u2026" })), !loading && error && (_jsx("div", { className: indexStyles.stateError, children: error })), !loading && !error && documents.length === 0 && (_jsx("div", { className: indexStyles.state, children: hasActiveFilters
|
|
47
46
|
? 'No documents match your filters.'
|
|
48
47
|
: 'No documents found. Documents added as task attachments will appear here.' })), !loading && documents.map(doc => {
|
|
49
48
|
const task = doc.taskId ? taskById.get(doc.taskId) : null;
|
|
@@ -4,10 +4,16 @@ export declare function schedulePrintCleanup(cleanup: () => void): number;
|
|
|
4
4
|
interface Props {
|
|
5
5
|
doc: DocMeta;
|
|
6
6
|
taskTitle: string | null;
|
|
7
|
+
tasks?: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
referenceLabel?: string | null;
|
|
11
|
+
}>;
|
|
7
12
|
apiBaseUrl?: string;
|
|
8
13
|
onSaved?: (updatedAt: string) => void;
|
|
9
14
|
onDeleted?: () => void;
|
|
15
|
+
onBackToTask?: (taskId: string) => void;
|
|
10
16
|
enableTaskGeneration?: boolean;
|
|
11
17
|
}
|
|
12
|
-
export declare function DocumentViewer({ doc, taskTitle, apiBaseUrl, onSaved, onDeleted, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function DocumentViewer({ doc, taskTitle, tasks, apiBaseUrl, onSaved, onDeleted, onBackToTask, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
|
|
13
19
|
export {};
|
|
@@ -4,10 +4,11 @@ import Markdown from 'react-markdown';
|
|
|
4
4
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
6
6
|
import rehypeSanitize from 'rehype-sanitize';
|
|
7
|
-
import { Printer, Link, Download, Clock, HardDrive, Tag, Pencil, X, Save, Eye, SplitSquareHorizontal, CheckCircle, AlertCircle, Zap, Trash2, MessageSquareText, Send, Bot, User, RotateCcw, CheckCheck } from 'lucide-react';
|
|
7
|
+
import { Printer, Link, Download, Clock, HardDrive, Tag, Pencil, X, Save, Eye, SplitSquareHorizontal, CheckCircle, AlertCircle, Zap, Trash2, MessageSquareText, Send, Bot, User, RotateCcw, CheckCheck, ArrowLeft } from 'lucide-react';
|
|
8
8
|
import styles from '../../Taskforce.module.css';
|
|
9
9
|
import viewerStyles from './DocumentViewer.module.css';
|
|
10
10
|
import taskFormStyles from '../task/TaskForm.module.css';
|
|
11
|
+
import { Modal } from '../ui/Modal';
|
|
11
12
|
import { getEffectiveAttachmentState } from './documentWorkspaceModel';
|
|
12
13
|
import { DocumentGenerateModal } from './DocumentGenerateModal';
|
|
13
14
|
import { fetchTaskforceApi, resolveTaskforceApiUrl } from '../../utils/taskforceApiClient';
|
|
@@ -500,7 +501,7 @@ function toggleNthTaskCheckbox(markdown, targetIndex, nextChecked) {
|
|
|
500
501
|
});
|
|
501
502
|
return updated.join('\n');
|
|
502
503
|
}
|
|
503
|
-
export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDeleted, enableTaskGeneration = false }) {
|
|
504
|
+
export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', onSaved, onDeleted, onBackToTask, enableTaskGeneration = false }) {
|
|
504
505
|
const fallbackReadableName = getReadableDocumentName(doc);
|
|
505
506
|
const [currentDocumentName, setCurrentDocumentName] = useState(fallbackReadableName);
|
|
506
507
|
const readableName = currentDocumentName;
|
|
@@ -532,6 +533,10 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
|
|
|
532
533
|
const [monochromeMode, setMonochromeMode] = useState(false);
|
|
533
534
|
const [deleting, setDeleting] = useState(false);
|
|
534
535
|
const [renaming, setRenaming] = useState(false);
|
|
536
|
+
const [attachModalOpen, setAttachModalOpen] = useState(false);
|
|
537
|
+
const [attachSearch, setAttachSearch] = useState('');
|
|
538
|
+
const [attachingTaskId, setAttachingTaskId] = useState(null);
|
|
539
|
+
const [attachError, setAttachError] = useState(null);
|
|
535
540
|
const [renameValue, setRenameValue] = useState(readableName);
|
|
536
541
|
const isEditable = doc.editable !== false;
|
|
537
542
|
const attachmentState = getEffectiveAttachmentState(doc);
|
|
@@ -545,6 +550,13 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
|
|
|
545
550
|
const reviewThreadEndRef = useRef(null);
|
|
546
551
|
const markdownContainerRef = useRef(null);
|
|
547
552
|
const activeReviewSession = reviewSessions.find((session) => session.id === selectedReviewSessionId) || reviewSessions[0] || null;
|
|
553
|
+
const filteredTasks = tasks.filter((task) => {
|
|
554
|
+
const query = attachSearch.trim().toLowerCase();
|
|
555
|
+
if (!query)
|
|
556
|
+
return true;
|
|
557
|
+
return task.title.toLowerCase().includes(query)
|
|
558
|
+
|| String(task.referenceLabel || '').trim().toLowerCase().includes(query);
|
|
559
|
+
});
|
|
548
560
|
const fetchContent = useCallback(async () => {
|
|
549
561
|
setLoadingContent(true);
|
|
550
562
|
setLoadError(null);
|
|
@@ -941,6 +953,44 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
|
|
|
941
953
|
setDeleting(false);
|
|
942
954
|
}
|
|
943
955
|
}, [apiBaseUrl, attachmentState, deleting, doc.assetId, doc.attachmentCount, onDeleted, readableName]);
|
|
956
|
+
const handleOpenAttachModal = useCallback(() => {
|
|
957
|
+
setAttachSearch('');
|
|
958
|
+
setAttachError(null);
|
|
959
|
+
setAttachingTaskId(null);
|
|
960
|
+
setAttachModalOpen(true);
|
|
961
|
+
}, []);
|
|
962
|
+
const handleAttachToTask = useCallback(async (taskId) => {
|
|
963
|
+
if (!doc.assetId)
|
|
964
|
+
return;
|
|
965
|
+
setAttachError(null);
|
|
966
|
+
setAttachingTaskId(taskId);
|
|
967
|
+
try {
|
|
968
|
+
const res = await fetchTaskforceApi(`/api/taskforce/documents/${encodeURIComponent(doc.assetId)}/attach`, {
|
|
969
|
+
method: 'POST',
|
|
970
|
+
credentials: 'include',
|
|
971
|
+
headers: { 'Content-Type': 'application/json' },
|
|
972
|
+
body: JSON.stringify({
|
|
973
|
+
targetType: 'task',
|
|
974
|
+
targetId: taskId,
|
|
975
|
+
}),
|
|
976
|
+
}, apiBaseUrl);
|
|
977
|
+
const data = await res.json().catch(() => ({}));
|
|
978
|
+
if (!res.ok)
|
|
979
|
+
throw new Error(data.error || `Attach failed (${res.status})`);
|
|
980
|
+
if (data.alreadyAttached) {
|
|
981
|
+
setAttachError('This document is already attached to that task.');
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
setAttachModalOpen(false);
|
|
985
|
+
onSaved?.(new Date().toISOString());
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
setAttachError(error?.message || 'Failed to attach document.');
|
|
989
|
+
}
|
|
990
|
+
finally {
|
|
991
|
+
setAttachingTaskId(null);
|
|
992
|
+
}
|
|
993
|
+
}, [apiBaseUrl, doc.assetId, onSaved]);
|
|
944
994
|
const handleToggleReviewStatus = useCallback(async () => {
|
|
945
995
|
if (!activeReviewSession || reviewBusy)
|
|
946
996
|
return;
|
|
@@ -1227,7 +1277,7 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
|
|
|
1227
1277
|
return;
|
|
1228
1278
|
setRenameValue(readableName);
|
|
1229
1279
|
setRenaming(true);
|
|
1230
|
-
}, children: readableName })), taskTitle && (_jsx("span", { className: viewerStyles.metaTaskName, title: `From task: ${taskTitle}`, children: taskTitle })), attachmentState === 'unattached' && (_jsx("span", { className: viewerStyles.statusBadge, children: "Unattached" })), doc.updatedAt && (_jsxs("span", { className: viewerStyles.metaDate, title: formatDate(doc.updatedAt), children: [_jsx(Clock, { size: 11 }), formatDate(doc.updatedAt)] })), _jsxs("span", { className: viewerStyles.metaSize, children: [_jsx(HardDrive, { size: 11 }), formatBytes(doc.sizeBytes)] }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedReference, onClick: () => { void handleCopyReference(); }, title: "Copy document reference" })), saveStatus.type === 'saved' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusSaved}`, children: [_jsx(CheckCircle, { size: 11 }), "Saved"] })), saveStatus.type === 'error' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusError}`, title: saveStatus.message, children: [_jsx(AlertCircle, { size: 11 }), saveStatus.message || 'Save failed'] })), isDirty && saveStatus.type !== 'saving' && (_jsx("span", { className: viewerStyles.unsaved, children: "Unsaved changes" }))] }), _jsx("div", { className: viewerStyles.actions, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: viewerStyles.layoutToggle, children: [_jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'editor' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('editor'), title: "Editor only", children: _jsx(Pencil, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'split' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('split'), title: "Split view", children: _jsx(SplitSquareHorizontal, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'preview' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('preview'), title: "Preview only", children: _jsx(Eye, { size: 12 }) })] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCancelEdit, title: "Discard changes", children: [_jsx(X, { size: 13 }), "Cancel"] }), _jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.saveBtn}`, onClick: handleSave, disabled: saveStatus.type === 'saving' || !isDirty, title: "Save (Ctrl+S)", children: [_jsx(Save, { size: 13 }), saveStatus.type === 'saving' ? 'Saving…' : 'Save'] })] })) : (_jsxs(_Fragment, { children: [doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setRenaming((prev) => !prev), title: "Rename document", children: [_jsx(Pencil, { size: 13 }), renaming ? 'Close Rename' : 'Rename'] })), isEditable && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.editBtn}`, onClick: handleEnterEdit, title: "Edit document", children: [_jsx(Pencil, { size: 13 }), "Edit"] })), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleToggleReviewSidebar, title: showReviewSidebar ? 'Hide review and comments' : 'Show review and comments', children: [_jsx(MessageSquareText, { size: 13 }), showReviewSidebar ? 'Hide Review' : 'Show Review'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCopyLink, title: "Copy link", children: [_jsx(Link, { size: 13 }), copied ? 'Copied!' : 'Copy link'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleDownload, title: "Download .md", children: [_jsx(Download, { size: 13 }), "Download"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handlePrint, title: "Print", children: [_jsx(Printer, { size: 13 }), "Print"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setMonochromeMode((prev) => !prev), title: "Toggle black and white style", children: [_jsx(Eye, { size: 13 }), monochromeMode ? 'Color' : 'B/W'] }), enableTaskGeneration && doc.docType === 'implementation-plan' && !editMode && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.generateBtn}`, onClick: () => setShowGenModal(true), title: "Generate tasks from this plan", children: [_jsx(Zap, { size: 13 }), "Generate Tasks"] })), canDelete && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.dangerBtn}`, onClick: () => { void handleDelete(); }, disabled: deleting, title: attachmentState === 'attached'
|
|
1280
|
+
}, children: readableName })), taskTitle && (_jsx("span", { className: viewerStyles.metaTaskName, title: `From task: ${taskTitle}`, children: taskTitle })), attachmentState === 'unattached' && (_jsx("span", { className: viewerStyles.statusBadge, children: "Unattached" })), doc.updatedAt && (_jsxs("span", { className: viewerStyles.metaDate, title: formatDate(doc.updatedAt), children: [_jsx(Clock, { size: 11 }), formatDate(doc.updatedAt)] })), _jsxs("span", { className: viewerStyles.metaSize, children: [_jsx(HardDrive, { size: 11 }), formatBytes(doc.sizeBytes)] }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedReference, onClick: () => { void handleCopyReference(); }, title: "Copy document reference" })), saveStatus.type === 'saved' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusSaved}`, children: [_jsx(CheckCircle, { size: 11 }), "Saved"] })), saveStatus.type === 'error' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusError}`, title: saveStatus.message, children: [_jsx(AlertCircle, { size: 11 }), saveStatus.message || 'Save failed'] })), isDirty && saveStatus.type !== 'saving' && (_jsx("span", { className: viewerStyles.unsaved, children: "Unsaved changes" }))] }), _jsx("div", { className: viewerStyles.actions, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: viewerStyles.layoutToggle, children: [_jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'editor' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('editor'), title: "Editor only", children: _jsx(Pencil, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'split' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('split'), title: "Split view", children: _jsx(SplitSquareHorizontal, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'preview' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('preview'), title: "Preview only", children: _jsx(Eye, { size: 12 }) })] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCancelEdit, title: "Discard changes", children: [_jsx(X, { size: 13 }), "Cancel"] }), _jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.saveBtn}`, onClick: handleSave, disabled: saveStatus.type === 'saving' || !isDirty, title: "Save (Ctrl+S)", children: [_jsx(Save, { size: 13 }), saveStatus.type === 'saving' ? 'Saving…' : 'Save'] })] })) : (_jsxs(_Fragment, { children: [doc.taskId && onBackToTask && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => onBackToTask(doc.taskId), title: "Back to task", "aria-label": "Back to task", children: [_jsx(ArrowLeft, { size: 13 }), "Back to task"] })), doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setRenaming((prev) => !prev), title: "Rename document", children: [_jsx(Pencil, { size: 13 }), renaming ? 'Close Rename' : 'Rename'] })), isEditable && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.editBtn}`, onClick: handleEnterEdit, title: "Edit document", children: [_jsx(Pencil, { size: 13 }), "Edit"] })), doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: handleOpenAttachModal, title: "Attach document to a task", children: [_jsx(Link, { size: 13 }), "Attach"] })), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleToggleReviewSidebar, title: showReviewSidebar ? 'Hide review and comments' : 'Show review and comments', children: [_jsx(MessageSquareText, { size: 13 }), showReviewSidebar ? 'Hide Review' : 'Show Review'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCopyLink, title: "Copy link", children: [_jsx(Link, { size: 13 }), copied ? 'Copied!' : 'Copy link'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleDownload, title: "Download .md", children: [_jsx(Download, { size: 13 }), "Download"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handlePrint, title: "Print", children: [_jsx(Printer, { size: 13 }), "Print"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setMonochromeMode((prev) => !prev), title: "Toggle black and white style", children: [_jsx(Eye, { size: 13 }), monochromeMode ? 'Color' : 'B/W'] }), enableTaskGeneration && doc.docType === 'implementation-plan' && !editMode && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.generateBtn}`, onClick: () => setShowGenModal(true), title: "Generate tasks from this plan", children: [_jsx(Zap, { size: 13 }), "Generate Tasks"] })), canDelete && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.dangerBtn}`, onClick: () => { void handleDelete(); }, disabled: deleting, title: attachmentState === 'attached'
|
|
1231
1281
|
? 'Remove this document from linked tasks and delete it'
|
|
1232
1282
|
: 'Permanently delete this unattached document', children: [_jsx(Trash2, { size: 13 }), deleting
|
|
1233
1283
|
? 'Deleting…'
|
|
@@ -1254,5 +1304,13 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
|
|
|
1254
1304
|
event.preventDefault();
|
|
1255
1305
|
void handleAddReviewComment();
|
|
1256
1306
|
}
|
|
1257
|
-
} })] }), _jsx("button", { type: "button", className: taskFormStyles.sendCommentBtn, onClick: () => { void handleAddReviewComment(); }, disabled: reviewBusy || !reviewDraft.trim() || activeReviewSession?.status === 'resolved', "aria-label": "Send review comment", title: "Send review comment", children: _jsx(Send, { size: 15 }) })] })] })] }) }))] }))), enableTaskGeneration && showGenModal && content !== null && (_jsx(DocumentGenerateModal, { content: content, docTitle: doc.title, apiBaseUrl: apiBaseUrl, onClose: () => setShowGenModal(false), onGenerated: (_count) => setShowGenModal(false) })),
|
|
1307
|
+
} })] }), _jsx("button", { type: "button", className: taskFormStyles.sendCommentBtn, onClick: () => { void handleAddReviewComment(); }, disabled: reviewBusy || !reviewDraft.trim() || activeReviewSession?.status === 'resolved', "aria-label": "Send review comment", title: "Send review comment", children: _jsx(Send, { size: 15 }) })] })] })] }) }))] }))), enableTaskGeneration && showGenModal && content !== null && (_jsx(DocumentGenerateModal, { content: content, docTitle: doc.title, apiBaseUrl: apiBaseUrl, onClose: () => setShowGenModal(false), onGenerated: (_count) => setShowGenModal(false) })), _jsx(Modal, { isOpen: attachModalOpen, onClose: () => {
|
|
1308
|
+
if (attachingTaskId)
|
|
1309
|
+
return;
|
|
1310
|
+
setAttachModalOpen(false);
|
|
1311
|
+
}, title: "Attach document", size: "md", footer: (_jsx("button", { className: viewerStyles.actionBtn, onClick: () => setAttachModalOpen(false), disabled: Boolean(attachingTaskId), children: "Close" })), children: _jsxs("div", { className: viewerStyles.attachModalBody, children: [_jsx("p", { className: viewerStyles.attachModalCopy, children: "Attach this document to a task from the document manager. Initiative and workstream targets can be enabled later on the same attachment model." }), _jsx("input", { type: "text", className: styles.input, placeholder: "Search tasks\u2026", value: attachSearch, onChange: (event) => setAttachSearch(event.target.value) }), attachError && (_jsxs("div", { className: viewerStyles.attachModalError, children: [_jsx(AlertCircle, { size: 14 }), _jsx("span", { children: attachError })] })), _jsx("div", { className: `${viewerStyles.attachTaskList} tf-scrollbar`, children: filteredTasks.length === 0 ? (_jsx("div", { className: viewerStyles.attachEmptyState, children: tasks.length === 0 ? 'No tasks are available in this workspace.' : 'No tasks match your search.' })) : (filteredTasks.map((task) => {
|
|
1312
|
+
const isCurrentPrimaryAttachment = doc.taskId === task.id;
|
|
1313
|
+
const isBusy = attachingTaskId === task.id;
|
|
1314
|
+
return (_jsxs("button", { type: "button", className: viewerStyles.attachTaskRow, disabled: isBusy, onClick: () => { void handleAttachToTask(task.id); }, children: [_jsxs("span", { className: viewerStyles.attachTaskInfo, children: [_jsx("span", { className: viewerStyles.attachTaskTitle, children: task.title }), task.referenceLabel && (_jsx("span", { className: viewerStyles.attachTaskReference, children: task.referenceLabel }))] }), _jsx("span", { className: viewerStyles.attachTaskMeta, children: isBusy ? 'Attaching…' : isCurrentPrimaryAttachment ? 'Attached' : 'Attach' })] }, task.id));
|
|
1315
|
+
})) })] }) }), !editMode && (_jsx("div", { className: viewerStyles.printHint, children: "For the cleanest output, disable browser Headers and footers in the print dialog." }))] }));
|
|
1258
1316
|
}
|
|
@@ -7,6 +7,7 @@ interface Props {
|
|
|
7
7
|
tasks: Array<{
|
|
8
8
|
id: string;
|
|
9
9
|
title: string;
|
|
10
|
+
referenceLabel?: string | null;
|
|
10
11
|
}>;
|
|
11
12
|
apiBaseUrl?: string;
|
|
12
13
|
documents: DocMeta[];
|
|
@@ -15,16 +16,20 @@ interface Props {
|
|
|
15
16
|
selectedDoc: DocMeta | null;
|
|
16
17
|
searchQuery: string;
|
|
17
18
|
hasActiveFilters: boolean;
|
|
19
|
+
documentTrayOpen?: boolean;
|
|
18
20
|
onSearchChange: (value: string) => void;
|
|
19
21
|
onSelectDoc: (doc: DocMeta | null) => void;
|
|
20
22
|
onRefresh: () => Promise<void> | void;
|
|
23
|
+
onCloseDocumentTray?: () => void;
|
|
21
24
|
onDeleteSelected: () => Promise<void> | void;
|
|
25
|
+
onBackToTask?: (taskId: string) => void;
|
|
22
26
|
enableTaskGeneration?: boolean;
|
|
23
27
|
}
|
|
24
28
|
interface ShellProps {
|
|
25
29
|
tasks: Array<{
|
|
26
30
|
id: string;
|
|
27
31
|
title: string;
|
|
32
|
+
referenceLabel?: string | null;
|
|
28
33
|
}>;
|
|
29
34
|
runtimeMode?: 'local' | 'cloud';
|
|
30
35
|
apiBaseUrl?: string;
|
|
@@ -33,10 +38,13 @@ interface ShellProps {
|
|
|
33
38
|
attachmentFilters?: DocumentAttachmentFilter[];
|
|
34
39
|
onTypeFiltersChange?: (values: DocumentTypeFilter[]) => void;
|
|
35
40
|
onAttachmentFiltersChange?: (values: DocumentAttachmentFilter[]) => void;
|
|
41
|
+
documentTrayOpen?: boolean;
|
|
42
|
+
onCloseDocumentTray?: () => void;
|
|
36
43
|
requestedDocPath?: string | null;
|
|
37
44
|
requestedDocAssetId?: string | null;
|
|
38
45
|
onRequestedDocHandled?: () => void;
|
|
46
|
+
onBackToTask?: (taskId: string) => void;
|
|
39
47
|
enableTaskGeneration?: boolean;
|
|
40
48
|
}
|
|
41
|
-
export declare function DocumentWorkspace({ tasks, apiBaseUrl, documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, onSearchChange, onSelectDoc, onRefresh, onDeleteSelected, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
|
|
42
|
-
export declare function DocumentWorkspaceShell({ tasks, runtimeMode, apiBaseUrl, cloudAuthBaseUrl, typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, requestedDocPath, requestedDocAssetId, onRequestedDocHandled, enableTaskGeneration }: ShellProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export declare function DocumentWorkspace({ tasks, apiBaseUrl, documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, documentTrayOpen, onSearchChange, onSelectDoc, onRefresh, onCloseDocumentTray, onDeleteSelected, onBackToTask, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare function DocumentWorkspaceShell({ tasks, runtimeMode, apiBaseUrl, cloudAuthBaseUrl, typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, documentTrayOpen, onCloseDocumentTray, requestedDocPath, requestedDocAssetId, onRequestedDocHandled, onBackToTask, enableTaskGeneration }: ShellProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import styles from '../../Taskforce.module.css';
|
|
3
4
|
import { DocumentIndex } from './DocumentIndex';
|
|
4
5
|
import { DocumentViewer } from './DocumentViewer';
|
|
5
6
|
import { useDocumentWorkspaceController } from './useDocumentWorkspaceController';
|
|
6
7
|
export { DOCUMENT_TYPE_FILTER_OPTIONS, DOCUMENT_ATTACHMENT_FILTER_OPTIONS } from './documentWorkspaceFilters';
|
|
7
|
-
export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, onSearchChange, onSelectDoc, onRefresh, onDeleteSelected, enableTaskGeneration = false }) {
|
|
8
|
+
export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, documentTrayOpen, onSearchChange, onSelectDoc, onRefresh, onCloseDocumentTray, onDeleteSelected, onBackToTask, enableTaskGeneration = false }) {
|
|
8
9
|
const taskById = new Map(tasks.map(t => [t.id, t]));
|
|
9
|
-
|
|
10
|
+
const useTrayIndex = typeof documentTrayOpen === 'boolean';
|
|
11
|
+
const documentIndex = (_jsx(DocumentIndex, { documents: documents, selectedDocId: selectedDoc?.id ?? null, loading: loading, error: error, searchQuery: searchQuery, onSearchChange: onSearchChange, onSelect: (doc) => onSelectDoc(doc), onCloseTray: useTrayIndex ? onCloseDocumentTray : undefined, taskById: taskById, hasActiveFilters: hasActiveFilters }));
|
|
12
|
+
return (_jsxs("div", { className: `${styles.docWorkspace} ${useTrayIndex ? styles.docWorkspaceTrayMode : ''} ${useTrayIndex && documentTrayOpen ? styles.docWorkspaceTrayOpen : ''}`.trim(), children: [useTrayIndex ? (_jsx("div", { className: `${styles.docTrayPanel} ${documentTrayOpen ? styles.docTrayPanelOpen : ''}`.trim(), "aria-hidden": !documentTrayOpen, children: documentIndex })) : (_jsx("div", { className: styles.docIndexPanel, children: documentIndex })), _jsx("div", { className: styles.docViewerPanel, children: selectedDoc ? (_jsx(DocumentViewer, { doc: selectedDoc, taskTitle: selectedDoc.taskId ? (taskById.get(selectedDoc.taskId)?.title ?? null) : null, tasks: tasks, apiBaseUrl: apiBaseUrl, onSaved: (_updatedAt) => onRefresh(), onDeleted: () => void onDeleteSelected(), onBackToTask: onBackToTask, enableTaskGeneration: enableTaskGeneration })) : !loading && (_jsx("div", { className: styles.docViewerEmpty, children: _jsx("p", { className: "tf-empty-copy", children: documents.length === 0 ? 'No documents match your current filters.' : 'Select a document to view it.' }) })) })] }));
|
|
10
13
|
}
|
|
11
|
-
export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, requestedDocPath = null, requestedDocAssetId = null, onRequestedDocHandled, enableTaskGeneration = false }) {
|
|
14
|
+
export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, documentTrayOpen, onCloseDocumentTray, requestedDocPath = null, requestedDocAssetId = null, onRequestedDocHandled, onBackToTask, enableTaskGeneration = false }) {
|
|
12
15
|
const { documentApiBaseUrl, filteredDocs, loading, error, selectedDoc, searchQuery, hasActiveFilters, setSearchQuery, fetchDocuments, selectDoc, handleDeleteSelected, } = useDocumentWorkspaceController({
|
|
13
16
|
runtimeMode,
|
|
14
17
|
apiBaseUrl,
|
|
@@ -21,5 +24,13 @@ export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUr
|
|
|
21
24
|
requestedDocAssetId,
|
|
22
25
|
onRequestedDocHandled,
|
|
23
26
|
});
|
|
24
|
-
|
|
27
|
+
const previousDocumentTrayOpenRef = React.useRef(documentTrayOpen);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
const wasOpen = previousDocumentTrayOpenRef.current === true;
|
|
30
|
+
previousDocumentTrayOpenRef.current = documentTrayOpen;
|
|
31
|
+
if (documentTrayOpen !== true || wasOpen)
|
|
32
|
+
return;
|
|
33
|
+
void fetchDocuments();
|
|
34
|
+
}, [documentTrayOpen, fetchDocuments]);
|
|
35
|
+
return (_jsx(DocumentWorkspace, { tasks: tasks, apiBaseUrl: documentApiBaseUrl, documents: filteredDocs, loading: loading, error: error, selectedDoc: selectedDoc, searchQuery: searchQuery, hasActiveFilters: hasActiveFilters, documentTrayOpen: documentTrayOpen, onSearchChange: setSearchQuery, onSelectDoc: selectDoc, onRefresh: fetchDocuments, onCloseDocumentTray: onCloseDocumentTray, onDeleteSelected: handleDeleteSelected, onBackToTask: onBackToTask, enableTaskGeneration: enableTaskGeneration }));
|
|
25
36
|
}
|
|
@@ -45,7 +45,7 @@ function createEmptyReviewSessionsResponse() {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
function renderDocumentWorkspace(props) {
|
|
48
|
-
return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
|
|
48
|
+
return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, documentTrayOpen: props.documentTrayOpen, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
|
|
49
49
|
}
|
|
50
50
|
describe('Document workspace management', () => {
|
|
51
51
|
beforeEach(() => {
|
|
@@ -97,6 +97,16 @@ describe('Document workspace management', () => {
|
|
|
97
97
|
expect(screen.queryByRole('button', { name: /Attached notes/i })).not.toBeInTheDocument();
|
|
98
98
|
expect(screen.getByText('Unattached')).toBeInTheDocument();
|
|
99
99
|
});
|
|
100
|
+
it('can render the document index as a collapsible tray', () => {
|
|
101
|
+
const docs = [createDoc({ id: 'tray-doc', displayName: 'Tray Document' })];
|
|
102
|
+
const { container, rerender } = renderDocumentWorkspace({
|
|
103
|
+
documents: docs,
|
|
104
|
+
documentTrayOpen: true,
|
|
105
|
+
});
|
|
106
|
+
expect(container.querySelector('[aria-hidden="false"]')).toHaveTextContent('Tray Document');
|
|
107
|
+
rerender(_jsx(DocumentWorkspace, { tasks: [], documents: docs, loading: false, error: null, selectedDoc: null, searchQuery: "", hasActiveFilters: false, documentTrayOpen: false, onSearchChange: () => { }, onSelectDoc: () => { }, onRefresh: () => { }, onDeleteSelected: () => { } }));
|
|
108
|
+
expect(container.querySelector('[aria-hidden="true"]')).toHaveTextContent('Tray Document');
|
|
109
|
+
});
|
|
100
110
|
it('keeps document search in the index without duplicate filter controls', () => {
|
|
101
111
|
renderDocumentWorkspace({ documents: [createDoc()] });
|
|
102
112
|
expect(screen.getByPlaceholderText('Search documents…')).toBeInTheDocument();
|
|
@@ -145,6 +155,46 @@ describe('Document workspace management', () => {
|
|
|
145
155
|
confirmSpy.mockRestore();
|
|
146
156
|
alertSpy.mockRestore();
|
|
147
157
|
});
|
|
158
|
+
it('opens an attach modal from the document viewer and attaches the document to a task', async () => {
|
|
159
|
+
const user = userEvent.setup();
|
|
160
|
+
fetchTaskforceApi
|
|
161
|
+
.mockResolvedValueOnce({
|
|
162
|
+
ok: true,
|
|
163
|
+
text: async () => '# Example\n',
|
|
164
|
+
})
|
|
165
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
166
|
+
.mockResolvedValueOnce({
|
|
167
|
+
ok: true,
|
|
168
|
+
json: async () => ({ success: true, attached: true, alreadyAttached: false }),
|
|
169
|
+
});
|
|
170
|
+
const onSaved = vi.fn();
|
|
171
|
+
render(_jsx(DocumentViewer, { doc: createDoc({
|
|
172
|
+
id: 'doc-attach',
|
|
173
|
+
assetId: 'asset-doc-attach',
|
|
174
|
+
attachmentCount: 0,
|
|
175
|
+
attachmentState: 'unattached',
|
|
176
|
+
}), taskTitle: null, tasks: [
|
|
177
|
+
{ id: 'task-1', title: 'First attach target', referenceLabel: 'T-11' },
|
|
178
|
+
{ id: 'task-2', title: 'Second attach target', referenceLabel: 'T-12' },
|
|
179
|
+
], onSaved: onSaved }));
|
|
180
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-1/content', { credentials: 'same-origin' }, ''));
|
|
181
|
+
await user.click(screen.getByRole('button', { name: /^Attach$/i }));
|
|
182
|
+
expect(screen.getByText(/Attach this document to a task from the document manager/i)).toBeInTheDocument();
|
|
183
|
+
expect(screen.getByText('T-11')).toBeInTheDocument();
|
|
184
|
+
expect(screen.getByText('T-12')).toBeInTheDocument();
|
|
185
|
+
await user.clear(screen.getByPlaceholderText('Search tasks…'));
|
|
186
|
+
await user.type(screen.getByPlaceholderText('Search tasks…'), 'T-12');
|
|
187
|
+
expect(screen.queryByRole('button', { name: /First attach target/i })).not.toBeInTheDocument();
|
|
188
|
+
expect(screen.getByRole('button', { name: /Second attach target/i })).toBeInTheDocument();
|
|
189
|
+
await user.clear(screen.getByPlaceholderText('Search tasks…'));
|
|
190
|
+
await user.type(screen.getByPlaceholderText('Search tasks…'), 'Second');
|
|
191
|
+
await user.click(screen.getByRole('button', { name: /Second attach target/i }));
|
|
192
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attach/attach', expect.objectContaining({
|
|
193
|
+
method: 'POST',
|
|
194
|
+
credentials: 'include',
|
|
195
|
+
}), ''));
|
|
196
|
+
expect(onSaved).toHaveBeenCalled();
|
|
197
|
+
});
|
|
148
198
|
it('shows delete and unlink for attached docs and sends forceDetach on delete', async () => {
|
|
149
199
|
const user = userEvent.setup();
|
|
150
200
|
fetchTaskforceApi
|
|
@@ -208,11 +208,13 @@ export interface TaskSettingsModel {
|
|
|
208
208
|
cleanupWarnings?: string[];
|
|
209
209
|
}>;
|
|
210
210
|
onMcpHostRootChange?: (value: string) => void;
|
|
211
|
+
onOpenCloudAuth?: (mode: 'login' | 'register') => void;
|
|
211
212
|
isAuthenticated?: boolean;
|
|
212
213
|
}
|
|
213
214
|
interface TaskSettingsProps {
|
|
214
215
|
settingsModel: TaskSettingsModel;
|
|
215
216
|
onSectionChange?: (section: any) => void;
|
|
217
|
+
renderMode?: 'full' | 'mcp-only';
|
|
216
218
|
}
|
|
217
219
|
export declare function TaskSettings(props: TaskSettingsProps): import("react/jsx-runtime").JSX.Element;
|
|
218
220
|
export {};
|