@taskforcehq/taskforce 0.3.303 → 0.3.305

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.
Files changed (237) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +102 -5
  3. package/dist/TaskforceCore.js +635 -238
  4. package/dist/TaskforceCore.test.js +2243 -495
  5. package/dist/annotatedAttachments/service.d.ts +1 -1
  6. package/dist/annotatedAttachments/types.d.ts +1 -1
  7. package/dist/compat/workspaceSyncCompat.js +6 -0
  8. package/dist/components/features/AgentsModule.js +1 -1
  9. package/dist/components/features/AgentsModule.test.js +1 -1
  10. package/dist/components/features/DocumentIndex.d.ts +1 -1
  11. package/dist/components/features/DocumentIndex.js +1 -1
  12. package/dist/components/features/DocumentViewer.d.ts +1 -1
  13. package/dist/components/features/DocumentViewer.js +32 -9
  14. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  15. package/dist/components/features/DocumentWorkspace.js +12 -155
  16. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  17. package/dist/components/features/TaskSettings.js +87 -41
  18. package/dist/components/features/TaskSettings.test.js +180 -40
  19. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  20. package/dist/components/features/documentWorkspaceModel.js +57 -0
  21. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  22. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  23. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  24. package/dist/components/task/TaskActionHeader.js +3 -3
  25. package/dist/components/task/TaskActionHeader.test.js +8 -0
  26. package/dist/components/task/TaskCard.js +16 -1
  27. package/dist/components/task/TaskCard.test.js +31 -0
  28. package/dist/components/task/TaskForm.d.ts +9 -8
  29. package/dist/components/task/TaskForm.js +374 -219
  30. package/dist/components/task/TaskForm.test.js +482 -361
  31. package/dist/components/task/TaskKanban.d.ts +1 -1
  32. package/dist/components/task/TaskKanban.js +33 -161
  33. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  34. package/dist/components/task/TaskStatusActions.js +8 -3
  35. package/dist/components/task/TaskStatusActions.test.js +9 -0
  36. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  37. package/dist/components/task/taskKanbanDropRules.js +187 -0
  38. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  39. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  40. package/dist/components/ui/Modal.d.ts +1 -1
  41. package/dist/components/ui/Modal.js +1 -0
  42. package/dist/components/views/AppShellHeader.js +5 -1
  43. package/dist/components/views/AppShellHeader.test.js +4 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  45. package/dist/components/views/PlanComparisonPage.js +9 -10
  46. package/dist/components/views/PlanComparisonPage.test.js +55 -41
  47. package/dist/components/views/PlansPage.d.ts +5 -1
  48. package/dist/components/views/PlansPage.js +150 -81
  49. package/dist/components/views/PlansPage.test.js +367 -41
  50. package/dist/components/views/StandaloneLayout.js +135 -54
  51. package/dist/components/views/WidgetView.js +11 -2
  52. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  53. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  54. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  55. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  56. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  57. package/dist/config/envSchema.js +1 -1
  58. package/dist/core/AiProfileService.d.ts +6 -1
  59. package/dist/core/AiProfileService.js +161 -16
  60. package/dist/core/AiProfileService.test.js +3 -1
  61. package/dist/core/AiProfiles.test.js +200 -0
  62. package/dist/core/AuthTokenService.test.d.ts +1 -0
  63. package/dist/core/AuthTokenService.test.js +78 -0
  64. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  65. package/dist/core/EntitlementsPolicy.test.js +75 -13
  66. package/dist/core/PlanEntitlementService.d.ts +9 -1
  67. package/dist/core/PlanEntitlementService.js +144 -19
  68. package/dist/core/PlanVersionPolicy.test.js +19 -26
  69. package/dist/core/SignupMonetizationSettings.test.js +75 -15
  70. package/dist/core/SystemAdmin.test.js +212 -56
  71. package/dist/core/TaskActivityService.d.ts +4 -1
  72. package/dist/core/TaskActivityService.js +45 -0
  73. package/dist/core/TaskAuditTimeline.test.js +52 -1
  74. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  75. package/dist/core/Taskforce.d.ts +18 -11
  76. package/dist/core/Taskforce.js +93 -12
  77. package/dist/core/WorkspacePlanMode.test.js +73 -8
  78. package/dist/core/shared.d.ts +2 -0
  79. package/dist/core/shared.js +11 -0
  80. package/dist/documentReviews/service.d.ts +1 -1
  81. package/dist/documentReviews/types.d.ts +1 -1
  82. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  83. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  84. package/dist/hooks/sync/auth.d.ts +37 -0
  85. package/dist/hooks/sync/auth.js +63 -0
  86. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  87. package/dist/hooks/sync/bootstrap.js +52 -0
  88. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  89. package/dist/hooks/sync/orchestratorShared.js +209 -0
  90. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  91. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  92. package/dist/hooks/sync/realtime.d.ts +25 -0
  93. package/dist/hooks/sync/realtime.js +60 -0
  94. package/dist/hooks/sync/recovery.d.ts +69 -0
  95. package/dist/hooks/sync/recovery.js +118 -0
  96. package/dist/hooks/sync/transfers.d.ts +123 -0
  97. package/dist/hooks/sync/transfers.js +435 -0
  98. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  99. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  100. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  101. package/dist/hooks/ui/useResourceExport.js +5 -2
  102. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  103. package/dist/hooks/useRealtimeSync.js +2 -1
  104. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  105. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  106. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  107. package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
  108. package/dist/hooks/useSyncOrchestrator.js +388 -879
  109. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
  110. package/dist/hooks/useTaskMutations.d.ts +2 -1
  111. package/dist/hooks/useTaskMutations.js +57 -7
  112. package/dist/hooks/useTaskMutations.test.js +176 -0
  113. package/dist/hooks/useTaskforce.d.ts +13 -4
  114. package/dist/hooks/useTaskforce.js +302 -817
  115. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  116. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  117. package/dist/hooks/useWorkspaceSyncController.js +33 -11
  118. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  119. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  120. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  121. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  122. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  123. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  124. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  125. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  126. package/dist/localization/locales/en-US.js +2 -2
  127. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  128. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  129. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  130. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  131. package/dist/mcp/clientIntegrations.d.ts +12 -1
  132. package/dist/mcp/clientIntegrations.js +161 -6
  133. package/dist/mcp/clientIntegrations.test.js +76 -4
  134. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  135. package/dist/mcp/collaborationRegistrar.js +71 -0
  136. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  137. package/dist/mcp/documentAssetRegistrar.js +346 -0
  138. package/dist/mcp/documentHelpers.d.ts +100 -0
  139. package/dist/mcp/documentHelpers.js +161 -0
  140. package/dist/mcp/runtime.js +2455 -3925
  141. package/dist/mcp/runtime.test.js +240 -8
  142. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  143. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  144. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  145. package/dist/mcp/taskAttachmentTypes.js +1 -0
  146. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  147. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  148. package/dist/mcp/toolCatalog.d.ts +35 -0
  149. package/dist/mcp/toolCatalog.js +3 -0
  150. package/dist/plugins/vite.js +1 -1
  151. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  152. package/dist/runtime/appGateDefinitions.js +1 -1
  153. package/dist/server/index.d.ts +1 -0
  154. package/dist/server/index.js +46 -3
  155. package/dist/server/index.test.js +12 -1
  156. package/dist/server/routes/admin.d.ts +1 -5
  157. package/dist/server/routes/admin.js +151 -80
  158. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  159. package/dist/server/routes/annotatedAttachments.js +1 -1
  160. package/dist/server/routes/auth.d.ts +1 -4
  161. package/dist/server/routes/auth.js +59 -64
  162. package/dist/server/routes/authSupport.d.ts +30 -0
  163. package/dist/server/routes/authSupport.js +81 -0
  164. package/dist/server/routes/billing.d.ts +1 -1
  165. package/dist/server/routes/billing.js +122 -255
  166. package/dist/server/routes/billing.test.js +257 -86
  167. package/dist/server/routes/documentReviews.d.ts +1 -1
  168. package/dist/server/routes/documentReviews.js +1 -1
  169. package/dist/server/routes/documents.d.ts +4 -2
  170. package/dist/server/routes/documents.js +73 -7
  171. package/dist/server/routes/primitives.d.ts +11 -0
  172. package/dist/server/routes/primitives.js +73 -0
  173. package/dist/server/routes/resources.d.ts +1 -1
  174. package/dist/server/routes/resources.js +1 -1
  175. package/dist/server/routes/shared.d.ts +31 -1
  176. package/dist/server/routes/shared.js +26 -0
  177. package/dist/server/routes/sync.d.ts +1 -1
  178. package/dist/server/routes/sync.integration.test.js +20 -0
  179. package/dist/server/routes/sync.js +111 -238
  180. package/dist/server/routes/tasks.d.ts +1 -1
  181. package/dist/server/routes/tasks.js +50 -8
  182. package/dist/server/routes/workspaces.d.ts +1 -1
  183. package/dist/server/routes/workspaces.js +1 -1
  184. package/dist/server/routes.d.ts +2 -9
  185. package/dist/server/routes.js +84 -181
  186. package/dist/server/routes.test.js +912 -32
  187. package/dist/shared/taskActor.d.ts +10 -0
  188. package/dist/shared/taskActor.js +1 -0
  189. package/dist/sync/collaborationSyncPayload.js +4 -0
  190. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  191. package/dist/sync/collaborationSyncState.d.ts +3 -1
  192. package/dist/sync/collaborationSyncState.js +19 -0
  193. package/dist/sync/contentSyncState.js +28 -17
  194. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  195. package/dist/sync/syncApplyHandlers.js +370 -8
  196. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  197. package/dist/sync/syncApplyHandlers.test.js +379 -0
  198. package/dist/sync/syncService.d.ts +9 -1
  199. package/dist/sync/syncService.js +44 -2
  200. package/dist/sync/syncService.test.js +62 -0
  201. package/dist/sync/workspacePullFeed.d.ts +1 -0
  202. package/dist/sync/workspacePullFeed.js +66 -59
  203. package/dist/sync/workspacePullFeed.test.js +72 -4
  204. package/dist/sync/workspaceSyncModel.d.ts +25 -1
  205. package/dist/sync/workspaceSyncModel.js +77 -3
  206. package/dist/sync/workspaceSyncModel.test.js +153 -0
  207. package/dist/sync/workspaceSyncState.d.ts +2 -0
  208. package/dist/sync/workspaceSyncState.js +1 -0
  209. package/dist/types.d.ts +2 -10
  210. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
  211. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  212. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  213. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  214. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  215. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  216. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  217. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  218. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
  219. package/dist/ui/assets/index-C2-OXZV7.css +1 -0
  220. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  221. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
  222. package/dist/ui/index.html +4 -4
  223. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  224. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  225. package/dist/utils/taskActivity.js +4 -0
  226. package/dist/utils/taskActivity.test.js +25 -1
  227. package/package.json +10 -1
  228. package/scripts/check-cloud-mcp.mjs +83 -0
  229. package/scripts/playwright-auth.sh +9 -1
  230. package/scripts/run-billing-performance-smoke.sh +24 -0
  231. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  232. package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
  233. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  234. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  235. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  236. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  237. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -1,54 +1,186 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { render, screen, waitFor } from '@testing-library/react';
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
3
  import { afterEach, describe, expect, it, vi } from 'vitest';
4
4
  import { MemoryRouter } from 'react-router-dom';
5
5
  import { PlansPage } from './PlansPage';
6
6
  vi.mock('./PlanComparisonPage', () => ({
7
- PlanComparisonPage: ({ heading, statusBanner }) => (_jsxs("div", { children: [_jsx("h1", { children: heading }), statusBanner?.message ? _jsx("div", { children: statusBanner.message }) : null] }))
7
+ PlanComparisonPage: ({ heading, statusBanner, topActions, onSelectPlan, onBack, backLabel, backDisabled }) => (_jsxs("div", { children: [_jsx("h1", { children: heading }), statusBanner?.message ? _jsx("div", { "data-status-type": statusBanner.type, children: statusBanner.message }) : null, topActions, onSelectPlan ? (_jsx("button", { onClick: () => onSelectPlan({
8
+ planId: 'free',
9
+ planVersionId: 'free-v1',
10
+ interval: 'month',
11
+ pricingType: 'free',
12
+ isCurrentPlan: false
13
+ }), children: "Select Free Plan" })) : null, onBack ? (_jsx("button", { onClick: onBack, disabled: backDisabled, children: backLabel || 'Back' })) : null] }))
8
14
  }));
9
15
  describe('PlansPage', () => {
10
16
  afterEach(() => {
17
+ vi.useRealTimers();
11
18
  vi.unstubAllGlobals();
12
19
  });
13
- it('shows recovery messaging instead of false checkout success when confirmation fails', async () => {
14
- const fetchMock = vi.fn()
15
- .mockResolvedValueOnce({
16
- ok: true,
17
- json: async () => ({
18
- planId: 'pro',
19
- planVersionId: 'pro-v1',
20
- entitlementState: 'checkout_pending',
21
- gate: 'checkout_pending',
22
- message: 'Complete checkout to continue.'
23
- })
24
- })
25
- .mockResolvedValueOnce({
26
- ok: false,
27
- json: async () => ({
28
- error: 'Checkout session has not completed yet.',
29
- code: 'CHECKOUT_SESSION_INCOMPLETE'
30
- })
31
- })
32
- .mockResolvedValueOnce({
33
- ok: true,
34
- json: async () => ({
35
- planId: 'pro',
36
- planVersionId: 'pro-v1',
37
- entitlementState: 'checkout_pending',
38
- gate: 'plan_selection_required',
39
- message: 'Your previous checkout expired. Choose a plan to continue.',
40
- planSelectionRequired: true
41
- })
20
+ it('shows recovery messaging only after bounded retries are exhausted', async () => {
21
+ let profileSummaryCalls = 0;
22
+ let confirmCalls = 0;
23
+ const fetchMock = vi.fn(async (url) => {
24
+ if (url.includes('/api/taskforce/account/profile-summary')) {
25
+ profileSummaryCalls += 1;
26
+ if (profileSummaryCalls <= 2) {
27
+ return {
28
+ ok: true,
29
+ json: async () => ({
30
+ planId: 'basic',
31
+ planVersionId: 'basic-v1',
32
+ entitlementState: 'checkout_pending',
33
+ gate: 'checkout_pending',
34
+ message: 'Complete checkout to continue.'
35
+ })
36
+ };
37
+ }
38
+ return {
39
+ ok: true,
40
+ json: async () => ({
41
+ planId: 'basic',
42
+ planVersionId: 'basic-v1',
43
+ entitlementState: 'pending_plan_selection',
44
+ gate: 'plan_selection_required',
45
+ message: 'Your previous checkout expired. Choose a plan to continue.',
46
+ planSelectionRequired: true
47
+ })
48
+ };
49
+ }
50
+ if (url.includes('/api/taskforce/billing/checkout-session/confirm')) {
51
+ confirmCalls += 1;
52
+ return {
53
+ ok: false,
54
+ status: 409,
55
+ json: async () => ({
56
+ error: 'Checkout session has not completed yet.',
57
+ code: 'CHECKOUT_SESSION_INCOMPLETE'
58
+ })
59
+ };
60
+ }
61
+ return {
62
+ ok: true,
63
+ json: async () => ({})
64
+ };
42
65
  });
43
66
  vi.stubGlobal('fetch', fetchMock);
44
67
  render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=success&session_id=cs_test_123'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u1", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
45
68
  expect(await screen.findByText('Checkout completed successfully. Finalizing your plan...')).toBeInTheDocument();
46
- expect(await screen.findByText('Your previous checkout expired. Choose a plan to continue.')).toBeInTheDocument();
69
+ expect(confirmCalls).toBe(1);
70
+ await waitFor(() => {
71
+ expect(confirmCalls).toBe(4);
72
+ }, { timeout: 4500 });
73
+ await waitFor(() => {
74
+ expect(screen.getByText('Your previous checkout expired. Choose a plan to continue.')).toBeInTheDocument();
75
+ });
47
76
  expect(screen.queryByText('Checkout completed successfully.')).not.toBeInTheDocument();
77
+ }, 10000);
78
+ it('retries checkout confirmation when the first confirm is incomplete and later succeeds', async () => {
79
+ let profileSummaryCalls = 0;
80
+ let confirmCalls = 0;
81
+ const fetchMock = vi.fn(async (url) => {
82
+ if (url.includes('/api/taskforce/account/profile-summary')) {
83
+ profileSummaryCalls += 1;
84
+ if (profileSummaryCalls <= 2) {
85
+ return {
86
+ ok: true,
87
+ json: async () => ({
88
+ planId: 'basic',
89
+ planVersionId: 'basic-v1',
90
+ entitlementState: 'checkout_pending',
91
+ gate: 'checkout_pending',
92
+ message: 'Complete checkout to continue.'
93
+ })
94
+ };
95
+ }
96
+ return {
97
+ ok: true,
98
+ json: async () => ({
99
+ entitlementState: 'active',
100
+ planId: 'basic',
101
+ planVersionId: 'basic-v1',
102
+ billingInterval: 'month',
103
+ gate: 'ok',
104
+ message: null
105
+ })
106
+ };
107
+ }
108
+ if (url.includes('/api/taskforce/billing/checkout-session/confirm')) {
109
+ confirmCalls += 1;
110
+ if (confirmCalls === 1) {
111
+ return {
112
+ ok: false,
113
+ status: 409,
114
+ json: async () => ({
115
+ error: 'Checkout session has not completed yet.',
116
+ code: 'CHECKOUT_SESSION_INCOMPLETE'
117
+ })
118
+ };
119
+ }
120
+ return {
121
+ ok: true,
122
+ status: 200,
123
+ json: async () => ({ success: true })
124
+ };
125
+ }
126
+ return {
127
+ ok: true,
128
+ json: async () => ({})
129
+ };
130
+ });
131
+ vi.stubGlobal('fetch', fetchMock);
132
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=success&session_id=cs_test_retry_success'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u1", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
133
+ expect(await screen.findByText('Checkout completed successfully. Finalizing your plan...')).toBeInTheDocument();
134
+ expect(await screen.findByText('Checkout completed successfully.')).toBeInTheDocument();
135
+ expect(confirmCalls).toBe(2);
136
+ }, 10000);
137
+ it('does not attempt checkout confirmation before auth session is resolved', async () => {
138
+ let profileSummaryCalls = 0;
139
+ const fetchMock = vi.fn(async (url) => {
140
+ if (url.includes('/api/taskforce/account/profile-summary')) {
141
+ profileSummaryCalls += 1;
142
+ if (profileSummaryCalls <= 2) {
143
+ return {
144
+ ok: true,
145
+ json: async () => ({
146
+ planId: 'basic',
147
+ planVersionId: 'basic-v1',
148
+ entitlementState: 'checkout_pending',
149
+ gate: 'checkout_pending',
150
+ message: 'Complete checkout to continue.'
151
+ })
152
+ };
153
+ }
154
+ return {
155
+ ok: true,
156
+ json: async () => ({
157
+ entitlementState: 'active',
158
+ planId: 'basic',
159
+ planVersionId: 'basic-v1',
160
+ gate: 'ok',
161
+ message: null
162
+ })
163
+ };
164
+ }
165
+ if (url.includes('/api/taskforce/billing/checkout-session/confirm')) {
166
+ return {
167
+ ok: true,
168
+ status: 200,
169
+ json: async () => ({ success: true })
170
+ };
171
+ }
172
+ return {
173
+ ok: true,
174
+ json: async () => ({})
175
+ };
176
+ });
177
+ vi.stubGlobal('fetch', fetchMock);
178
+ const view = render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=success&session_id=cs_test_auth_wait'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u_auth", runtimeMode: "cloud", authSessionResolved: false, resolveCloudAuthUrl: (path) => path }) }));
179
+ expect(await screen.findByText('Checkout completed successfully. Finalizing your plan...')).toBeInTheDocument();
180
+ expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/billing/checkout-session/confirm'))).toBe(false);
181
+ view.rerender(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=success&session_id=cs_test_auth_wait'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u_auth", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
48
182
  await waitFor(() => {
49
- expect(fetchMock).toHaveBeenNthCalledWith(2, '/api/taskforce/billing/checkout-session/confirm', expect.objectContaining({
50
- method: 'POST'
51
- }));
183
+ expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/api/taskforce/billing/checkout-session/confirm'))).toBe(true);
52
184
  });
53
185
  });
54
186
  it('shows conflict-specific recovery messaging when the account lands on plans already blocked', async () => {
@@ -56,8 +188,8 @@ describe('PlansPage', () => {
56
188
  .mockResolvedValueOnce({
57
189
  ok: true,
58
190
  json: async () => ({
59
- planId: 'pro',
60
- planVersionId: 'pro-v1',
191
+ planId: 'basic',
192
+ planVersionId: 'basic-v1',
61
193
  entitlementState: 'pending_plan_selection',
62
194
  gate: 'plan_selection_required',
63
195
  message: 'Choose a plan to continue.',
@@ -74,8 +206,8 @@ describe('PlansPage', () => {
74
206
  .mockResolvedValueOnce({
75
207
  ok: true,
76
208
  json: async () => ({
77
- planId: 'pro',
78
- planVersionId: 'pro-v1',
209
+ planId: 'basic',
210
+ planVersionId: 'basic-v1',
79
211
  entitlementState: 'checkout_pending',
80
212
  gate: 'checkout_pending',
81
213
  message: 'Complete checkout to continue.'
@@ -84,8 +216,8 @@ describe('PlansPage', () => {
84
216
  .mockResolvedValueOnce({
85
217
  ok: true,
86
218
  json: async () => ({
87
- planId: 'pro',
88
- planVersionId: 'pro-v1',
219
+ planId: 'basic',
220
+ planVersionId: 'basic-v1',
89
221
  entitlementState: 'pending_plan_selection',
90
222
  gate: 'plan_selection_required',
91
223
  message: 'Choose a plan to continue.',
@@ -97,4 +229,198 @@ describe('PlansPage', () => {
97
229
  render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=cancel'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u3", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
98
230
  expect(await screen.findByText('Your previous checkout expired. Choose a plan to continue.')).toBeInTheDocument();
99
231
  });
232
+ it('auto-continues after a free plan grant and exposes a taskforce CTA once access is active', async () => {
233
+ const onContinueToTaskforce = vi.fn();
234
+ const fetchMock = vi.fn()
235
+ .mockResolvedValueOnce({
236
+ ok: true,
237
+ json: async () => ({
238
+ entitlementState: 'pending_plan_selection',
239
+ gate: 'plan_selection_required',
240
+ message: 'Choose a plan to continue.'
241
+ })
242
+ })
243
+ .mockResolvedValueOnce({
244
+ ok: true,
245
+ json: async () => ({
246
+ checkoutState: 'free'
247
+ })
248
+ })
249
+ .mockResolvedValueOnce({
250
+ ok: true,
251
+ json: async () => ({
252
+ entitlementState: 'active',
253
+ planId: 'free',
254
+ planVersionId: 'free-v1',
255
+ gate: 'ok',
256
+ message: null
257
+ })
258
+ });
259
+ vi.stubGlobal('fetch', fetchMock);
260
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&gate=plan_selection_required'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u4", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path, onContinueToTaskforce: onContinueToTaskforce }) }));
261
+ expect(await screen.findByText('Choose a plan to continue.')).toBeInTheDocument();
262
+ fireEvent.click(screen.getByRole('button', { name: 'Select Free Plan' }));
263
+ await waitFor(() => {
264
+ expect(onContinueToTaskforce).toHaveBeenCalledTimes(1);
265
+ });
266
+ expect(screen.getByRole('button', { name: 'Take Me to Taskforce' })).toBeInTheDocument();
267
+ });
268
+ it('disables the back action while the account is still entitlement-blocked', async () => {
269
+ const fetchMock = vi.fn()
270
+ .mockResolvedValueOnce({
271
+ ok: true,
272
+ json: async () => ({
273
+ entitlementState: 'pending_plan_selection',
274
+ gate: 'plan_selection_required',
275
+ message: 'Choose a plan to continue.'
276
+ })
277
+ });
278
+ vi.stubGlobal('fetch', fetchMock);
279
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&gate=plan_selection_required'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u5", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
280
+ expect(await screen.findByRole('button', { name: 'Back to App' })).toBeDisabled();
281
+ expect(screen.getByText('Choose a plan to continue.')).toHaveAttribute('data-status-type', 'info');
282
+ });
283
+ it('does not auto-continue after an in-place paid interval update for an already active account', async () => {
284
+ const onContinueToTaskforce = vi.fn();
285
+ let profileSummaryCalls = 0;
286
+ const fetchMock = vi.fn(async (url) => {
287
+ if (url.includes('/api/taskforce/account/profile-summary')) {
288
+ profileSummaryCalls += 1;
289
+ if (profileSummaryCalls <= 2) {
290
+ return {
291
+ ok: true,
292
+ json: async () => ({
293
+ entitlementState: 'active',
294
+ planId: 'basic',
295
+ planVersionId: 'basic-v1',
296
+ gate: 'ok',
297
+ message: null
298
+ })
299
+ };
300
+ }
301
+ return {
302
+ ok: true,
303
+ json: async () => ({
304
+ entitlementState: 'active',
305
+ planId: 'basic',
306
+ planVersionId: 'basic-v1',
307
+ billingInterval: 'year',
308
+ gate: 'ok',
309
+ message: null
310
+ })
311
+ };
312
+ }
313
+ if (url.includes('/api/taskforce/billing/checkout-session')) {
314
+ return {
315
+ ok: true,
316
+ json: async () => ({
317
+ checkoutState: 'updated',
318
+ message: 'Your subscription was updated successfully.'
319
+ })
320
+ };
321
+ }
322
+ return {
323
+ ok: true,
324
+ json: async () => ({})
325
+ };
326
+ });
327
+ vi.stubGlobal('fetch', fetchMock);
328
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=start&planId=basic&planVersionId=basic-v1&interval=year'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u6", runtimeMode: "local", authSessionResolved: true, resolveCloudAuthUrl: (path) => path, onContinueToTaskforce: onContinueToTaskforce }) }));
329
+ expect(await screen.findByText('Your subscription was updated successfully.')).toBeInTheDocument();
330
+ expect(onContinueToTaskforce).not.toHaveBeenCalled();
331
+ });
332
+ it('does not auto-continue after confirming checkout when the account was already active', async () => {
333
+ const onContinueToTaskforce = vi.fn();
334
+ let profileSummaryCalls = 0;
335
+ const fetchMock = vi.fn(async (url) => {
336
+ if (url.includes('/api/taskforce/account/profile-summary')) {
337
+ profileSummaryCalls += 1;
338
+ if (profileSummaryCalls <= 2) {
339
+ return {
340
+ ok: true,
341
+ json: async () => ({
342
+ entitlementState: 'active',
343
+ planId: 'basic',
344
+ planVersionId: 'basic-v1',
345
+ gate: 'ok',
346
+ message: null
347
+ })
348
+ };
349
+ }
350
+ return {
351
+ ok: true,
352
+ json: async () => ({
353
+ entitlementState: 'active',
354
+ planId: 'team-5',
355
+ planVersionId: 'team-5-v1',
356
+ gate: 'ok',
357
+ message: null
358
+ })
359
+ };
360
+ }
361
+ if (url.includes('/api/taskforce/billing/checkout-session/confirm')) {
362
+ return {
363
+ ok: true,
364
+ json: async () => ({ success: true })
365
+ };
366
+ }
367
+ return {
368
+ ok: true,
369
+ json: async () => ({})
370
+ };
371
+ });
372
+ vi.stubGlobal('fetch', fetchMock);
373
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans&checkout=success&session_id=cs_test_existing_active'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u7", runtimeMode: "local", authSessionResolved: true, resolveCloudAuthUrl: (path) => path, onContinueToTaskforce: onContinueToTaskforce }) }));
374
+ expect(await screen.findByText('Checkout completed successfully.')).toBeInTheDocument();
375
+ expect(onContinueToTaskforce).not.toHaveBeenCalled();
376
+ });
377
+ it('shows trial-specific messaging while still allowing return to Taskforce', async () => {
378
+ const fetchMock = vi.fn().mockResolvedValue({
379
+ ok: true,
380
+ json: async () => ({
381
+ entitlementState: 'trialing',
382
+ planId: 'starter',
383
+ planVersionId: 'starter-v1',
384
+ gate: 'ok',
385
+ trialEnd: '2026-05-01T12:00:00.000Z'
386
+ })
387
+ });
388
+ vi.stubGlobal('fetch', fetchMock);
389
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u8", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path, onContinueToTaskforce: vi.fn() }) }));
390
+ expect(await screen.findByText(/Your trial is active through/)).toBeInTheDocument();
391
+ expect(screen.getByRole('button', { name: 'Take Me to Taskforce' })).toBeEnabled();
392
+ });
393
+ it('shows suspended accounts a billing-recovery message and keeps return blocked', async () => {
394
+ const fetchMock = vi.fn().mockResolvedValue({
395
+ ok: true,
396
+ json: async () => ({
397
+ entitlementState: 'suspended',
398
+ planId: 'pro',
399
+ planVersionId: 'pro-v1',
400
+ gate: 'missing_entitlement',
401
+ stripeCustomerId: 'cus_123'
402
+ })
403
+ });
404
+ vi.stubGlobal('fetch', fetchMock);
405
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u9", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
406
+ expect(await screen.findByText('Your access is suspended because billing needs attention. Update billing to restore access.')).toBeInTheDocument();
407
+ expect(screen.getByRole('button', { name: 'Back to App' })).toBeDisabled();
408
+ expect(screen.getByText('Your access is suspended because billing needs attention. Update billing to restore access.')).toHaveAttribute('data-status-type', 'error');
409
+ });
410
+ it('shows canceled accounts reactivation messaging instead of generic inactive copy', async () => {
411
+ const fetchMock = vi.fn().mockResolvedValue({
412
+ ok: true,
413
+ json: async () => ({
414
+ entitlementState: 'canceled',
415
+ planId: 'pro',
416
+ planVersionId: 'pro-v1',
417
+ gate: 'missing_entitlement',
418
+ message: 'This generic message should be ignored.'
419
+ })
420
+ });
421
+ vi.stubGlobal('fetch', fetchMock);
422
+ render(_jsx(MemoryRouter, { initialEntries: ['/?screen=plans'], children: _jsx(PlansPage, { isAuthenticated: true, authUserId: "u10", runtimeMode: "cloud", authSessionResolved: true, resolveCloudAuthUrl: (path) => path }) }));
423
+ expect(await screen.findByText('Your access has ended. Choose a plan to reactivate your workspace.')).toBeInTheDocument();
424
+ expect(screen.queryByText('This generic message should be ignored.')).not.toBeInTheDocument();
425
+ });
100
426
  });