@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { areSyncEventListsEqual, formatSyncEventLine, getSyncEventStableKey, mergeRecentSyncEvents, } from './syncEventPresentation';
|
|
3
|
+
describe('syncEventPresentation', () => {
|
|
4
|
+
it('formats blocked manual retry events with the local reason and phase', () => {
|
|
5
|
+
const line = formatSyncEventLine({
|
|
6
|
+
occurredAt: '2026-04-18T18:00:00.000Z',
|
|
7
|
+
eventType: 'pull',
|
|
8
|
+
status: 'error',
|
|
9
|
+
errorMessage: 'Manual sync retry blocked: sign in is required.',
|
|
10
|
+
details: {
|
|
11
|
+
source: 'retry.blocked',
|
|
12
|
+
reason: 'auth-required',
|
|
13
|
+
phase: 'active'
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
expect(line).toBe('2026-04-18T18:00:00.000Z manual retry blocked: sign in required during active sync');
|
|
17
|
+
});
|
|
18
|
+
it('formats manual repair start events with the chosen repair phase', () => {
|
|
19
|
+
const line = formatSyncEventLine({
|
|
20
|
+
occurredAt: '2026-04-18T18:05:00.000Z',
|
|
21
|
+
eventType: 'bootstrap',
|
|
22
|
+
status: 'success',
|
|
23
|
+
errorMessage: 'Manual sync repair started.',
|
|
24
|
+
details: {
|
|
25
|
+
source: 'repair.started',
|
|
26
|
+
phase: 'error',
|
|
27
|
+
repairPhase: 'attach-cloud'
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
expect(line).toBe('2026-04-18T18:05:00.000Z manual repair started: initial cloud pull selected');
|
|
31
|
+
});
|
|
32
|
+
it('falls back to the generic event formatter for normal sync events', () => {
|
|
33
|
+
const line = formatSyncEventLine({
|
|
34
|
+
occurredAt: '2026-04-18T18:10:00.000Z',
|
|
35
|
+
eventType: 'push',
|
|
36
|
+
status: 'success',
|
|
37
|
+
changeCount: 3,
|
|
38
|
+
requestMs: 42
|
|
39
|
+
});
|
|
40
|
+
expect(line).toBe('2026-04-18T18:10:00.000Z push succeeded (3 changes) in 42ms');
|
|
41
|
+
});
|
|
42
|
+
it('deduplicates and compares event lists using stable keys', () => {
|
|
43
|
+
const event = {
|
|
44
|
+
occurredAt: '2026-04-18T18:10:00.000Z',
|
|
45
|
+
eventType: 'push',
|
|
46
|
+
status: 'success',
|
|
47
|
+
changeCount: 1
|
|
48
|
+
};
|
|
49
|
+
const merged = mergeRecentSyncEvents([event], [event], 15);
|
|
50
|
+
expect(merged).toHaveLength(1);
|
|
51
|
+
expect(areSyncEventListsEqual(merged, [event])).toBe(true);
|
|
52
|
+
expect(getSyncEventStableKey(event)).toContain('2026-04-18T18:10:00.000Z');
|
|
53
|
+
});
|
|
54
|
+
it('builds stable keys even when event details contain circular or bigint values', () => {
|
|
55
|
+
const circular = { count: 3n };
|
|
56
|
+
circular.self = circular;
|
|
57
|
+
expect(() => getSyncEventStableKey({
|
|
58
|
+
occurredAt: '2026-04-18T18:10:00.000Z',
|
|
59
|
+
eventType: 'apply',
|
|
60
|
+
status: 'error',
|
|
61
|
+
details: circular
|
|
62
|
+
})).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { HeaderWorkspaceSyncStatus } from './workspaceSyncPresentation';
|
|
2
|
+
import { type SyncEventRecord } from './syncEventPresentation';
|
|
3
|
+
export interface SyncStatusMeta {
|
|
4
|
+
label: string;
|
|
5
|
+
icon: 'off' | 'cloud';
|
|
6
|
+
color: string;
|
|
7
|
+
border: string;
|
|
8
|
+
background: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ReferenceMismatchSummary {
|
|
11
|
+
key: string;
|
|
12
|
+
pathLabel: string;
|
|
13
|
+
refsLabel?: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface SyncSummaryItem {
|
|
16
|
+
label: string;
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SyncEventRow {
|
|
20
|
+
key: string;
|
|
21
|
+
text: string;
|
|
22
|
+
tone?: 'error' | 'muted' | 'default';
|
|
23
|
+
}
|
|
24
|
+
export interface WorkspaceSyncDiagnosticsPresentation {
|
|
25
|
+
lastErrorAt?: string | null;
|
|
26
|
+
pushBlockedReason?: string | null;
|
|
27
|
+
pullBlockedReason?: string | null;
|
|
28
|
+
retryBlockedReason?: string | null;
|
|
29
|
+
repairActive?: boolean;
|
|
30
|
+
pushInFlight?: boolean;
|
|
31
|
+
pullInFlight?: boolean;
|
|
32
|
+
retryPending?: boolean;
|
|
33
|
+
aiProfileSnapshotCount: number;
|
|
34
|
+
aiProfileSnapshotRawCount: number;
|
|
35
|
+
lastPushedAiProfileCount: number;
|
|
36
|
+
lastPushedAiProfileWatermarkCount: number;
|
|
37
|
+
aiProfileSnapshotLastFetchAt?: string | null;
|
|
38
|
+
aiProfileSnapshotLastFetchError?: string | null;
|
|
39
|
+
aiProfileSnapshotLastSkipReason?: string | null;
|
|
40
|
+
documentSnapshotCount: number;
|
|
41
|
+
assetSnapshotCount: number;
|
|
42
|
+
forceFullAiProfilePushQueued?: boolean;
|
|
43
|
+
}
|
|
44
|
+
interface BuildSyncEventRowsInput {
|
|
45
|
+
syncRecentEvents: SyncEventRecord[];
|
|
46
|
+
syncRecentEventsError: string | null;
|
|
47
|
+
syncRecentEventsLoading: boolean;
|
|
48
|
+
}
|
|
49
|
+
interface BuildCopySyncDetailsTextInput {
|
|
50
|
+
currentWorkspaceId: string;
|
|
51
|
+
syncStatusLabel: string;
|
|
52
|
+
workspaceSyncSummary: string;
|
|
53
|
+
workspaceSyncRecommendedAction: string;
|
|
54
|
+
formattedLastSyncTime: string;
|
|
55
|
+
formattedLastPullTime: string;
|
|
56
|
+
formattedLastPushTime: string;
|
|
57
|
+
workspaceSyncDiagnostics: WorkspaceSyncDiagnosticsPresentation;
|
|
58
|
+
workspaceSyncPendingChanges: string | number;
|
|
59
|
+
syncLastError: string;
|
|
60
|
+
syncStageLabel: string;
|
|
61
|
+
referenceMismatchCount: number;
|
|
62
|
+
syncRecentEvents: SyncEventRecord[];
|
|
63
|
+
}
|
|
64
|
+
export declare function resolveSyncStatusMeta(status: HeaderWorkspaceSyncStatus): SyncStatusMeta;
|
|
65
|
+
export declare function resolveSyncStageLabel(workspaceSyncPhase: string, workspaceSyncRepairBusy: boolean, workspaceSyncBusy: boolean): string;
|
|
66
|
+
export declare function buildSyncDiagnosticsSummary(workspaceSyncDiagnostics: WorkspaceSyncDiagnosticsPresentation): SyncSummaryItem[];
|
|
67
|
+
export declare function buildSyncEventRows({ syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading, }: BuildSyncEventRowsInput): SyncEventRow[];
|
|
68
|
+
export declare function getActiveReferenceMismatchEvents(events: SyncEventRecord[]): SyncEventRecord[];
|
|
69
|
+
export declare function getActiveReferenceMismatchCount(events: SyncEventRecord[]): number;
|
|
70
|
+
export declare function buildReferenceMismatchSummaries(events: SyncEventRecord[]): ReferenceMismatchSummary[];
|
|
71
|
+
export declare function buildCopySyncDetailsText({ currentWorkspaceId, syncStatusLabel, workspaceSyncSummary, workspaceSyncRecommendedAction, formattedLastSyncTime, formattedLastPullTime, formattedLastPushTime, workspaceSyncDiagnostics, workspaceSyncPendingChanges, syncLastError, syncStageLabel, referenceMismatchCount, syncRecentEvents, }: BuildCopySyncDetailsTextInput): string;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { formatSyncEventLine, getSyncEventStableKey, } from './syncEventPresentation';
|
|
2
|
+
function formatDiagnosticsTimestamp(value) {
|
|
3
|
+
if (!value)
|
|
4
|
+
return 'Never';
|
|
5
|
+
const parsed = new Date(value);
|
|
6
|
+
if (Number.isNaN(parsed.getTime()))
|
|
7
|
+
return 'Never';
|
|
8
|
+
return parsed.toLocaleString();
|
|
9
|
+
}
|
|
10
|
+
export function resolveSyncStatusMeta(status) {
|
|
11
|
+
switch (status) {
|
|
12
|
+
case 'off':
|
|
13
|
+
return {
|
|
14
|
+
label: 'Off',
|
|
15
|
+
icon: 'off',
|
|
16
|
+
color: '#94a3b8',
|
|
17
|
+
border: 'rgba(148, 163, 184, 0.35)',
|
|
18
|
+
background: 'rgba(148, 163, 184, 0.12)',
|
|
19
|
+
};
|
|
20
|
+
case 'syncing':
|
|
21
|
+
return {
|
|
22
|
+
label: 'Syncing',
|
|
23
|
+
icon: 'cloud',
|
|
24
|
+
color: '#60a5fa',
|
|
25
|
+
border: 'rgba(96, 165, 250, 0.35)',
|
|
26
|
+
background: 'rgba(59, 130, 246, 0.12)',
|
|
27
|
+
};
|
|
28
|
+
case 'attention':
|
|
29
|
+
return {
|
|
30
|
+
label: 'Needs Attention',
|
|
31
|
+
icon: 'cloud',
|
|
32
|
+
color: '#fda4af',
|
|
33
|
+
border: 'rgba(253, 164, 175, 0.4)',
|
|
34
|
+
background: 'rgba(239, 68, 68, 0.12)',
|
|
35
|
+
};
|
|
36
|
+
default:
|
|
37
|
+
return {
|
|
38
|
+
label: 'Healthy',
|
|
39
|
+
icon: 'cloud',
|
|
40
|
+
color: '#86efac',
|
|
41
|
+
border: 'rgba(134, 239, 172, 0.4)',
|
|
42
|
+
background: 'rgba(34, 197, 94, 0.12)',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function resolveSyncStageLabel(workspaceSyncPhase, workspaceSyncRepairBusy, workspaceSyncBusy) {
|
|
47
|
+
if (workspaceSyncRepairBusy) {
|
|
48
|
+
return 'Repairing';
|
|
49
|
+
}
|
|
50
|
+
if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
|
|
51
|
+
return 'Recovering';
|
|
52
|
+
}
|
|
53
|
+
switch (workspaceSyncPhase) {
|
|
54
|
+
case 'provision-local':
|
|
55
|
+
return 'Initial cloud upload';
|
|
56
|
+
case 'attach-cloud':
|
|
57
|
+
return 'Initial cloud pull';
|
|
58
|
+
case 'active':
|
|
59
|
+
return 'Active';
|
|
60
|
+
case 'error':
|
|
61
|
+
return 'Error';
|
|
62
|
+
default:
|
|
63
|
+
return 'Idle';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function buildSyncDiagnosticsSummary(workspaceSyncDiagnostics) {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
label: 'AI profile snapshot',
|
|
70
|
+
value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: 'AI profile raw response',
|
|
74
|
+
value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: 'Last pushed AI profiles',
|
|
78
|
+
value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'AI watermark map',
|
|
82
|
+
value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'Document snapshot',
|
|
86
|
+
value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
label: 'Asset snapshot',
|
|
90
|
+
value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'Queued full AI sync',
|
|
94
|
+
value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: 'AI snapshot last fetch',
|
|
98
|
+
value: formatDiagnosticsTimestamp(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt),
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'AI snapshot fetch error',
|
|
102
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: 'AI snapshot skip reason',
|
|
106
|
+
value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None',
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
export function buildSyncEventRows({ syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading, }) {
|
|
111
|
+
if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
|
|
112
|
+
return [{
|
|
113
|
+
key: 'loading',
|
|
114
|
+
text: 'Loading recent sync events...',
|
|
115
|
+
tone: 'muted',
|
|
116
|
+
}];
|
|
117
|
+
}
|
|
118
|
+
if (syncRecentEventsError) {
|
|
119
|
+
return [{
|
|
120
|
+
key: 'error',
|
|
121
|
+
text: syncRecentEventsError,
|
|
122
|
+
tone: 'error',
|
|
123
|
+
}];
|
|
124
|
+
}
|
|
125
|
+
if (syncRecentEvents.length === 0) {
|
|
126
|
+
return [{
|
|
127
|
+
key: 'empty',
|
|
128
|
+
text: 'No recent sync events recorded.',
|
|
129
|
+
tone: 'muted',
|
|
130
|
+
}];
|
|
131
|
+
}
|
|
132
|
+
return syncRecentEvents.map((event, index) => ({
|
|
133
|
+
key: getSyncEventStableKey(event, index),
|
|
134
|
+
text: formatSyncEventLine(event),
|
|
135
|
+
tone: event.status === 'error' ? 'error' : 'default',
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
export function getActiveReferenceMismatchEvents(events) {
|
|
139
|
+
const mismatches = [];
|
|
140
|
+
for (const event of events) {
|
|
141
|
+
const message = String(event.errorMessage || '').toLowerCase();
|
|
142
|
+
if (message.includes('reference number mismatch')) {
|
|
143
|
+
mismatches.push(event);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (String(event.status || '').toLowerCase() === 'success') {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return mismatches;
|
|
151
|
+
}
|
|
152
|
+
export function getActiveReferenceMismatchCount(events) {
|
|
153
|
+
return getActiveReferenceMismatchEvents(events).length;
|
|
154
|
+
}
|
|
155
|
+
export function buildReferenceMismatchSummaries(events) {
|
|
156
|
+
return getActiveReferenceMismatchEvents(events).map((event, index) => {
|
|
157
|
+
const details = event.details && typeof event.details === 'object' ? event.details : null;
|
|
158
|
+
const pathValue = String(details?.path || '').trim();
|
|
159
|
+
const referenceLabelValue = String(details?.referenceLabel || '').trim();
|
|
160
|
+
const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
|
|
161
|
+
const existingRefValue = Number(details?.existingReferenceNumber);
|
|
162
|
+
const incomingRefValue = Number(details?.incomingReferenceNumber);
|
|
163
|
+
const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
|
|
164
|
+
const refsLabel = (Number.isFinite(existingRefValue)
|
|
165
|
+
|| Number.isFinite(incomingRefValue))
|
|
166
|
+
? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
|
|
167
|
+
: referenceLabelValue
|
|
168
|
+
? `Both claimed ${referenceLabelValue}`
|
|
169
|
+
: null;
|
|
170
|
+
return {
|
|
171
|
+
key: getSyncEventStableKey(event, index),
|
|
172
|
+
pathLabel,
|
|
173
|
+
refsLabel,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
export function buildCopySyncDetailsText({ currentWorkspaceId, syncStatusLabel, workspaceSyncSummary, workspaceSyncRecommendedAction, formattedLastSyncTime, formattedLastPullTime, formattedLastPushTime, workspaceSyncDiagnostics, workspaceSyncPendingChanges, syncLastError, syncStageLabel, referenceMismatchCount, syncRecentEvents, }) {
|
|
178
|
+
const details = [
|
|
179
|
+
'Taskforce Sync Manager',
|
|
180
|
+
`Workspace ID: ${currentWorkspaceId}`,
|
|
181
|
+
`Status: ${syncStatusLabel}`,
|
|
182
|
+
`Summary: ${workspaceSyncSummary}`,
|
|
183
|
+
`Recommended action: ${workspaceSyncRecommendedAction}`,
|
|
184
|
+
`Last successful sync: ${formattedLastSyncTime}`,
|
|
185
|
+
`Last pull from cloud: ${formattedLastPullTime}`,
|
|
186
|
+
`Last push to cloud: ${formattedLastPushTime}`,
|
|
187
|
+
`Last error at: ${formatDiagnosticsTimestamp(workspaceSyncDiagnostics.lastErrorAt)}`,
|
|
188
|
+
`Pending local changes: ${workspaceSyncPendingChanges}`,
|
|
189
|
+
`Last error: ${syncLastError}`,
|
|
190
|
+
`Sync stage: ${syncStageLabel}`,
|
|
191
|
+
`Push blocked reason: ${workspaceSyncDiagnostics.pushBlockedReason || 'None'}`,
|
|
192
|
+
`Pull blocked reason: ${workspaceSyncDiagnostics.pullBlockedReason || 'None'}`,
|
|
193
|
+
`Retry blocked reason: ${workspaceSyncDiagnostics.retryBlockedReason || 'None'}`,
|
|
194
|
+
`Repair active: ${workspaceSyncDiagnostics.repairActive ? 'Yes' : 'No'}`,
|
|
195
|
+
`Push in flight: ${workspaceSyncDiagnostics.pushInFlight ? 'Yes' : 'No'}`,
|
|
196
|
+
`Pull in flight: ${workspaceSyncDiagnostics.pullInFlight ? 'Yes' : 'No'}`,
|
|
197
|
+
`Retry pending: ${workspaceSyncDiagnostics.retryPending ? 'Yes' : 'No'}`,
|
|
198
|
+
`AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
|
|
199
|
+
`AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
|
|
200
|
+
`Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
|
|
201
|
+
`AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
|
|
202
|
+
`AI snapshot last fetch: ${formatDiagnosticsTimestamp(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt)}`,
|
|
203
|
+
`AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
|
|
204
|
+
`AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
|
|
205
|
+
`Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
|
|
206
|
+
`Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
|
|
207
|
+
`Reference mismatches detected: ${referenceMismatchCount}`,
|
|
208
|
+
`Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`,
|
|
209
|
+
];
|
|
210
|
+
const recentEventLines = syncRecentEvents.map((event) => formatSyncEventLine(event));
|
|
211
|
+
return [
|
|
212
|
+
...details,
|
|
213
|
+
'',
|
|
214
|
+
'Recent sync events',
|
|
215
|
+
...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.']),
|
|
216
|
+
].join('\n');
|
|
217
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildCopySyncDetailsText, buildReferenceMismatchSummaries, buildSyncDiagnosticsSummary, buildSyncEventRows, getActiveReferenceMismatchCount, resolveSyncStageLabel, resolveSyncStatusMeta, } from './syncStatusPresentation';
|
|
3
|
+
describe('syncStatusPresentation', () => {
|
|
4
|
+
it('resolves sync status badges for visible header states', () => {
|
|
5
|
+
expect(resolveSyncStatusMeta('healthy')).toMatchObject({
|
|
6
|
+
label: 'Healthy',
|
|
7
|
+
icon: 'cloud',
|
|
8
|
+
});
|
|
9
|
+
expect(resolveSyncStatusMeta('attention')).toMatchObject({
|
|
10
|
+
label: 'Needs Attention',
|
|
11
|
+
icon: 'cloud',
|
|
12
|
+
});
|
|
13
|
+
expect(resolveSyncStatusMeta('off')).toMatchObject({
|
|
14
|
+
label: 'Off',
|
|
15
|
+
icon: 'off',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('describes startup and repair stages for the sync manager', () => {
|
|
19
|
+
expect(resolveSyncStageLabel('attach-cloud', false, true)).toBe('Initial cloud pull');
|
|
20
|
+
expect(resolveSyncStageLabel('error', true, true)).toBe('Repairing');
|
|
21
|
+
expect(resolveSyncStageLabel('error', false, true)).toBe('Recovering');
|
|
22
|
+
});
|
|
23
|
+
it('builds the diagnostics summary from sync snapshot counts', () => {
|
|
24
|
+
const summary = buildSyncDiagnosticsSummary({
|
|
25
|
+
aiProfileSnapshotCount: 2,
|
|
26
|
+
aiProfileSnapshotRawCount: 4,
|
|
27
|
+
lastPushedAiProfileCount: 3,
|
|
28
|
+
lastPushedAiProfileWatermarkCount: 5,
|
|
29
|
+
documentSnapshotCount: 1,
|
|
30
|
+
assetSnapshotCount: 2,
|
|
31
|
+
forceFullAiProfilePushQueued: true,
|
|
32
|
+
aiProfileSnapshotLastFetchAt: null,
|
|
33
|
+
aiProfileSnapshotLastFetchError: '',
|
|
34
|
+
aiProfileSnapshotLastSkipReason: null,
|
|
35
|
+
});
|
|
36
|
+
expect(summary).toContainEqual({
|
|
37
|
+
label: 'AI profile snapshot',
|
|
38
|
+
value: '2 local profiles',
|
|
39
|
+
});
|
|
40
|
+
expect(summary).toContainEqual({
|
|
41
|
+
label: 'Document snapshot',
|
|
42
|
+
value: '1 local doc',
|
|
43
|
+
});
|
|
44
|
+
expect(summary).toContainEqual({
|
|
45
|
+
label: 'Queued full AI sync',
|
|
46
|
+
value: 'Yes',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('keeps only active reference mismatches until the next successful sync event', () => {
|
|
50
|
+
const events = [
|
|
51
|
+
{
|
|
52
|
+
occurredAt: '2026-04-18T10:00:00.000Z',
|
|
53
|
+
status: 'error',
|
|
54
|
+
errorMessage: 'Detected task reference number mismatch during normal sync.',
|
|
55
|
+
details: {
|
|
56
|
+
path: 'docs/spec.md',
|
|
57
|
+
existingReferenceNumber: 14,
|
|
58
|
+
incomingReferenceNumber: 8,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
occurredAt: '2026-04-18T09:59:00.000Z',
|
|
63
|
+
status: 'success',
|
|
64
|
+
errorMessage: '',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
occurredAt: '2026-04-18T09:58:00.000Z',
|
|
68
|
+
status: 'error',
|
|
69
|
+
errorMessage: 'Detected task reference number mismatch during normal sync.',
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
expect(getActiveReferenceMismatchCount(events)).toBe(1);
|
|
73
|
+
expect(buildReferenceMismatchSummaries(events)).toEqual([
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
pathLabel: 'docs/spec.md',
|
|
76
|
+
refsLabel: 'Existing 14 vs incoming 8',
|
|
77
|
+
}),
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
it('builds readable sync event rows for loading, errors, and structured events', () => {
|
|
81
|
+
expect(buildSyncEventRows({
|
|
82
|
+
syncRecentEvents: [],
|
|
83
|
+
syncRecentEventsError: null,
|
|
84
|
+
syncRecentEventsLoading: true,
|
|
85
|
+
})).toEqual([{
|
|
86
|
+
key: 'loading',
|
|
87
|
+
text: 'Loading recent sync events...',
|
|
88
|
+
tone: 'muted',
|
|
89
|
+
}]);
|
|
90
|
+
expect(buildSyncEventRows({
|
|
91
|
+
syncRecentEvents: [],
|
|
92
|
+
syncRecentEventsError: 'Unable to load events.',
|
|
93
|
+
syncRecentEventsLoading: false,
|
|
94
|
+
})).toEqual([{
|
|
95
|
+
key: 'error',
|
|
96
|
+
text: 'Unable to load events.',
|
|
97
|
+
tone: 'error',
|
|
98
|
+
}]);
|
|
99
|
+
expect(buildSyncEventRows({
|
|
100
|
+
syncRecentEvents: [{
|
|
101
|
+
occurredAt: '2026-04-18T10:00:00.000Z',
|
|
102
|
+
status: 'error',
|
|
103
|
+
details: {
|
|
104
|
+
source: 'retry.blocked',
|
|
105
|
+
reason: 'lease-held',
|
|
106
|
+
phase: 'active',
|
|
107
|
+
},
|
|
108
|
+
}],
|
|
109
|
+
syncRecentEventsError: null,
|
|
110
|
+
syncRecentEventsLoading: false,
|
|
111
|
+
})[0]).toMatchObject({
|
|
112
|
+
tone: 'error',
|
|
113
|
+
text: '2026-04-18T10:00:00.000Z manual retry blocked: another window is already syncing during active sync',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('assembles the sync manager report text from the current view model', () => {
|
|
117
|
+
const report = buildCopySyncDetailsText({
|
|
118
|
+
currentWorkspaceId: 'workspace-123',
|
|
119
|
+
syncStatusLabel: 'Needs Attention',
|
|
120
|
+
workspaceSyncSummary: 'Sync is paused until you sign in again.',
|
|
121
|
+
workspaceSyncRecommendedAction: 'Sign in, then press Sync to retry.',
|
|
122
|
+
formattedLastSyncTime: '4/17/2026, 10:32:47 PM',
|
|
123
|
+
formattedLastPullTime: '4/17/2026, 10:32:47 PM',
|
|
124
|
+
formattedLastPushTime: '4/17/2026, 10:31:45 PM',
|
|
125
|
+
workspaceSyncDiagnostics: {
|
|
126
|
+
lastErrorAt: '2026-04-18T01:33:05.980Z',
|
|
127
|
+
pushBlockedReason: 'attach-cloud',
|
|
128
|
+
pullBlockedReason: null,
|
|
129
|
+
retryBlockedReason: 'auth-required',
|
|
130
|
+
repairActive: false,
|
|
131
|
+
pushInFlight: false,
|
|
132
|
+
pullInFlight: true,
|
|
133
|
+
retryPending: false,
|
|
134
|
+
aiProfileSnapshotCount: 14,
|
|
135
|
+
aiProfileSnapshotRawCount: 14,
|
|
136
|
+
lastPushedAiProfileCount: 14,
|
|
137
|
+
lastPushedAiProfileWatermarkCount: 14,
|
|
138
|
+
aiProfileSnapshotLastFetchAt: '2026-04-18T11:15:52.000Z',
|
|
139
|
+
aiProfileSnapshotLastFetchError: null,
|
|
140
|
+
aiProfileSnapshotLastSkipReason: null,
|
|
141
|
+
documentSnapshotCount: 127,
|
|
142
|
+
assetSnapshotCount: 56,
|
|
143
|
+
forceFullAiProfilePushQueued: false,
|
|
144
|
+
},
|
|
145
|
+
workspaceSyncPendingChanges: 31,
|
|
146
|
+
syncLastError: 'Detected task reference number mismatch during normal sync.',
|
|
147
|
+
syncStageLabel: 'Active',
|
|
148
|
+
referenceMismatchCount: 3,
|
|
149
|
+
syncRecentEvents: [{
|
|
150
|
+
occurredAt: '2026-04-18T01:33:06.044Z',
|
|
151
|
+
eventType: 'apply',
|
|
152
|
+
status: 'success',
|
|
153
|
+
changeCount: 200,
|
|
154
|
+
requestMs: 167,
|
|
155
|
+
statusCode: 0,
|
|
156
|
+
}],
|
|
157
|
+
});
|
|
158
|
+
expect(report).toContain('Workspace ID: workspace-123');
|
|
159
|
+
expect(report).toContain('Push blocked reason: attach-cloud');
|
|
160
|
+
expect(report).toContain('Retry blocked reason: auth-required');
|
|
161
|
+
expect(report).toContain('Reference mismatches detected: 3');
|
|
162
|
+
expect(report).toContain('2026-04-18T01:33:06.044Z apply succeeded (200 changes) in 167ms [0]');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -5,5 +5,7 @@ export declare function summarizeTaskChange(field: string, change: {
|
|
|
5
5
|
to?: unknown;
|
|
6
6
|
} | undefined, formatter?: TaskEventValueFormatter): string;
|
|
7
7
|
export declare function summarizeTaskEvent(event: TaskEvent, formatter?: TaskEventValueFormatter): string;
|
|
8
|
+
export declare function getTaskEventPrimaryChangeField(event: TaskEvent): string | null;
|
|
9
|
+
export declare function getTaskActivityItems(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem[];
|
|
8
10
|
export declare function getLatestTaskActivity(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem | null;
|
|
9
11
|
export declare function summarizeTaskActivity(item: TaskActivityItem | null, formatter?: TaskEventValueFormatter): string;
|