@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
@@ -7,18 +7,15 @@ import { PlanComparisonPage } from './PlanComparisonPage';
7
7
  import { AppShellHeader } from './AppShellHeader';
8
8
  import { resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
9
9
  import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
10
+ import { isCommercialEntitlementActive, resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
10
11
  const TRANSIENT_CHECKOUT_QUERY_CLEAR_DELAY_MS = 1600;
11
- const ACTIVE_ENTITLEMENT_STATES = new Set(['active', 'trialing', 'grace']);
12
- const GATE_MESSAGES = {
13
- planSelectionRequired: 'Choose a plan to continue.',
14
- checkoutPending: 'Complete checkout to continue.',
15
- misconfigured: 'Onboarding policy is misconfigured. Contact support or a system administrator.',
16
- missingEntitlement: 'No entitlement is linked to this account.'
17
- };
18
- const BILLING_CONFLICT_MESSAGES = {
19
- CHECKOUT_SESSION_EXPIRED: 'Your previous checkout expired. Choose a plan to continue.',
20
- CHECKOUT_SESSION_SUPERSEDED: 'A newer checkout replaced your previous checkout. Choose a plan to continue.'
21
- };
12
+ const CHECKOUT_CONFIRM_RETRY_DELAYS_MS = [0, 500, 1000, 2000];
13
+ function shouldRetryCheckoutConfirmation(statusCode, errorCode) {
14
+ if (statusCode === 401) {
15
+ return true;
16
+ }
17
+ return String(errorCode || '').trim().toUpperCase() === 'CHECKOUT_SESSION_INCOMPLETE';
18
+ }
22
19
  function parseInterval(raw) {
23
20
  return raw === 'year' ? 'year' : 'month';
24
21
  }
@@ -38,13 +35,14 @@ function buildEmbeddedPlansPath(params = {}) {
38
35
  }
39
36
  return `/?${query.toString()}`;
40
37
  }
41
- export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessionResolved, currentTheme = 'dark', projectName, currentWorkspaceId, apiBaseUrl, connectedEnvironmentSource, resolveCloudAuthUrl, embedded = false, shellOwnsScroll = false }) {
38
+ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessionResolved, currentTheme = 'dark', projectName, currentWorkspaceId, apiBaseUrl, connectedEnvironmentSource, resolveCloudAuthUrl, embedded = false, shellOwnsScroll = false, onAccountProfileSummaryChange, onContinueToTaskforce, continueBusy = false }) {
42
39
  const navigate = useNavigate();
43
40
  const location = useLocation();
44
41
  const [accountProfileSummary, setAccountProfileSummary] = useState(null);
45
42
  const [actionBusy, setActionBusy] = useState(false);
46
43
  const [feedback, setFeedback] = useState(null);
47
44
  const checkoutStartRan = useRef(false);
45
+ const checkoutSuccessHandledRef = useRef(null);
48
46
  const transientQueryClearTimeoutRef = useRef(null);
49
47
  const query = useMemo(() => new URLSearchParams(location.search), [location.search]);
50
48
  const checkoutState = String(query.get('checkout') || '').trim().toLowerCase();
@@ -89,32 +87,9 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
89
87
  ].map((value) => String(value || '').trim()).filter((value, index, all) => value && all.indexOf(value) === index);
90
88
  return endpoints;
91
89
  }, [normalizedApiBaseUrl]);
92
- const hasResolvedActiveEntitlement = useMemo(() => {
93
- const entitlementState = String(accountProfileSummary?.entitlementState || '').trim().toLowerCase();
94
- return ACTIVE_ENTITLEMENT_STATES.has(entitlementState);
95
- }, [accountProfileSummary?.entitlementState]);
96
- const gateMessage = useMemo(() => {
97
- if (hasResolvedActiveEntitlement)
98
- return null;
99
- const billingConflictCode = String(accountProfileSummary?.billingConflictCode || '').trim().toUpperCase();
100
- if (billingConflictCode === 'CHECKOUT_SESSION_EXPIRED')
101
- return BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_EXPIRED;
102
- if (billingConflictCode === 'CHECKOUT_SESSION_SUPERSEDED')
103
- return BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_SUPERSEDED;
104
- const summaryMessage = String(accountProfileSummary?.message || '').trim();
105
- if (summaryMessage)
106
- return summaryMessage;
107
- const effectiveGateState = String(accountProfileSummary?.gate || gateState || '').trim().toLowerCase();
108
- if (effectiveGateState === 'plan_selection_required')
109
- return GATE_MESSAGES.planSelectionRequired;
110
- if (effectiveGateState === 'checkout_pending')
111
- return GATE_MESSAGES.checkoutPending;
112
- if (effectiveGateState === 'misconfigured')
113
- return GATE_MESSAGES.misconfigured;
114
- if (effectiveGateState === 'missing_entitlement')
115
- return GATE_MESSAGES.missingEntitlement;
116
- return null;
117
- }, [accountProfileSummary?.billingConflictCode, accountProfileSummary?.gate, accountProfileSummary?.message, gateState, hasResolvedActiveEntitlement]);
90
+ const lifecyclePresentation = useMemo(() => resolveCommercialLifecyclePresentation(accountProfileSummary, gateState), [accountProfileSummary, gateState]);
91
+ const hasResolvedActiveEntitlement = useMemo(() => lifecyclePresentation.allowReturnToApp, [lifecyclePresentation.allowReturnToApp]);
92
+ const gateMessage = useMemo(() => lifecyclePresentation.message, [lifecyclePresentation.message]);
118
93
  const clearTransientQueryState = useCallback(() => {
119
94
  const next = new URLSearchParams(location.search);
120
95
  next.delete('checkout');
@@ -148,25 +123,24 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
148
123
  force: options?.force === true
149
124
  });
150
125
  setAccountProfileSummary(payload);
126
+ onAccountProfileSummaryChange?.(payload);
151
127
  return payload;
152
128
  }
153
129
  catch {
154
130
  // best effort only
155
131
  return null;
156
132
  }
157
- }, [authUserId, buildCloudBillingUrl, isAuthenticated]);
133
+ }, [authUserId, buildCloudBillingUrl, isAuthenticated, onAccountProfileSummaryChange]);
158
134
  const resolveRecoveryMessage = useCallback((summary, fallback) => {
159
- const billingConflictCode = String(summary?.billingConflictCode || '').trim().toUpperCase();
160
- if (billingConflictCode === 'CHECKOUT_SESSION_EXPIRED')
161
- return BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_EXPIRED;
162
- if (billingConflictCode === 'CHECKOUT_SESSION_SUPERSEDED')
163
- return BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_SUPERSEDED;
164
- const summaryMessage = String(summary?.message || '').trim();
165
- if (summaryMessage)
166
- return summaryMessage;
135
+ const message = resolveCommercialLifecyclePresentation(summary, gateState).message;
136
+ if (message)
137
+ return message;
167
138
  return String(fallback || 'Unable to confirm checkout completion.').trim() || 'Unable to confirm checkout completion.';
139
+ }, [gateState]);
140
+ const shouldAutoContinueAfterActivation = useCallback((summary, fallbackGate) => {
141
+ return !resolveCommercialLifecyclePresentation(summary, fallbackGate).allowReturnToApp;
168
142
  }, []);
169
- const startCheckout = useCallback(async (planVersionId, interval) => {
143
+ const startCheckout = useCallback(async (planVersionId, interval, options) => {
170
144
  if (!planVersionId) {
171
145
  setFeedback({ type: 'error', message: 'Selected plan is missing a default plan version.' });
172
146
  return;
@@ -189,13 +163,21 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
189
163
  if (checkoutState === 'free') {
190
164
  setFeedback({ type: 'success', message: 'Plan selected. Your access is ready.' });
191
165
  invalidateAccountProfileSummaryCache(buildCloudBillingUrl(ACCOUNT_PROFILE_SUMMARY_PATH), { identityKey: authUserId });
192
- await loadAccountProfileSummary();
166
+ const summary = await loadAccountProfileSummary({ force: true });
167
+ const summaryState = String(summary?.entitlementState || '').trim().toLowerCase();
168
+ if (options?.autoContinueOnActivation === true && isCommercialEntitlementActive(summaryState)) {
169
+ await onContinueToTaskforce?.();
170
+ }
193
171
  return;
194
172
  }
195
173
  if (checkoutState === 'updated') {
196
174
  setFeedback({ type: 'success', message: String(payload?.message || 'Your subscription was updated successfully.') });
197
175
  invalidateAccountProfileSummaryCache(buildCloudBillingUrl(ACCOUNT_PROFILE_SUMMARY_PATH), { identityKey: authUserId });
198
- await loadAccountProfileSummary();
176
+ const summary = await loadAccountProfileSummary({ force: true });
177
+ const summaryState = String(summary?.entitlementState || '').trim().toLowerCase();
178
+ if (options?.autoContinueOnActivation === true && isCommercialEntitlementActive(summaryState)) {
179
+ await onContinueToTaskforce?.();
180
+ }
199
181
  return;
200
182
  }
201
183
  if (checkoutState === 'already_current_plan') {
@@ -235,12 +217,13 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
235
217
  finally {
236
218
  setActionBusy(false);
237
219
  }
238
- }, [authUserId, buildCloudBillingUrl, loadAccountProfileSummary, runtimeMode]);
239
- const confirmCheckoutSession = useCallback(async (sessionId) => {
220
+ }, [authUserId, buildCloudBillingUrl, loadAccountProfileSummary, onContinueToTaskforce, runtimeMode]);
221
+ const confirmCheckoutSession = useCallback(async (sessionId, options) => {
240
222
  if (!sessionId) {
241
223
  return {
242
224
  confirmed: false,
243
- errorMessage: 'Checkout session is missing.'
225
+ errorMessage: 'Checkout session is missing.',
226
+ retryable: false
244
227
  };
245
228
  }
246
229
  try {
@@ -252,22 +235,29 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
252
235
  });
253
236
  const payload = await res.json().catch(() => ({}));
254
237
  if (!res.ok) {
238
+ const errorCode = String(payload?.code || '').trim();
255
239
  return {
256
240
  confirmed: false,
257
- errorMessage: String(payload?.error || 'Unable to confirm checkout completion.')
241
+ errorMessage: String(payload?.error || 'Unable to confirm checkout completion.'),
242
+ retryable: shouldRetryCheckoutConfirmation(res.status, errorCode)
258
243
  };
259
244
  }
260
245
  invalidateAccountProfileSummaryCache(buildCloudBillingUrl(ACCOUNT_PROFILE_SUMMARY_PATH), { identityKey: authUserId });
261
- await loadAccountProfileSummary();
246
+ const summary = await loadAccountProfileSummary({ force: true });
247
+ const summaryState = String(summary?.entitlementState || '').trim().toLowerCase();
248
+ if (options?.autoContinueOnActivation === true && isCommercialEntitlementActive(summaryState)) {
249
+ await onContinueToTaskforce?.();
250
+ }
262
251
  return { confirmed: true };
263
252
  }
264
253
  catch {
265
254
  return {
266
255
  confirmed: false,
267
- errorMessage: 'Unable to confirm checkout completion.'
256
+ errorMessage: 'Unable to confirm checkout completion.',
257
+ retryable: true
268
258
  };
269
259
  }
270
- }, [authUserId, buildCloudBillingUrl, loadAccountProfileSummary]);
260
+ }, [authUserId, buildCloudBillingUrl, loadAccountProfileSummary, onContinueToTaskforce]);
271
261
  const openBillingPortal = useCallback(async () => {
272
262
  if (!isAuthenticated) {
273
263
  navigate(`/login?mode=login&next=${encodeURIComponent(buildEmbeddedPlansPath())}`);
@@ -315,15 +305,84 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
315
305
  }
316
306
  }, []);
317
307
  useEffect(() => {
308
+ if (checkoutState !== 'success') {
309
+ checkoutSuccessHandledRef.current = null;
310
+ }
311
+ }, [checkoutState]);
312
+ useEffect(() => {
313
+ let cancelled = false;
314
+ const pendingRetryTimeouts = new Set();
315
+ const waitForRetry = async (delayMs) => {
316
+ if (delayMs <= 0)
317
+ return;
318
+ await new Promise((resolve) => {
319
+ const timeoutId = window.setTimeout(() => {
320
+ pendingRetryTimeouts.delete(timeoutId);
321
+ resolve();
322
+ }, delayMs);
323
+ pendingRetryTimeouts.add(timeoutId);
324
+ });
325
+ };
318
326
  if (checkoutState === 'success') {
319
327
  checkoutStartRan.current = true;
320
328
  setFeedback({ type: 'success', message: 'Checkout completed successfully. Finalizing your plan...' });
329
+ if (!authSessionResolved) {
330
+ return () => {
331
+ cancelled = true;
332
+ pendingRetryTimeouts.forEach((timeoutId) => window.clearTimeout(timeoutId));
333
+ pendingRetryTimeouts.clear();
334
+ };
335
+ }
336
+ if (!isAuthenticated) {
337
+ setFeedback({ type: 'error', message: 'Sign in to finish activating your plan.' });
338
+ return () => {
339
+ cancelled = true;
340
+ pendingRetryTimeouts.forEach((timeoutId) => window.clearTimeout(timeoutId));
341
+ pendingRetryTimeouts.clear();
342
+ };
343
+ }
344
+ const successFlowKey = querySessionId || '__missing_session__';
345
+ if (checkoutSuccessHandledRef.current === successFlowKey) {
346
+ return () => {
347
+ cancelled = true;
348
+ pendingRetryTimeouts.forEach((timeoutId) => window.clearTimeout(timeoutId));
349
+ pendingRetryTimeouts.clear();
350
+ };
351
+ }
352
+ checkoutSuccessHandledRef.current = successFlowKey;
321
353
  void (async () => {
322
- const confirmation = querySessionId
323
- ? await confirmCheckoutSession(querySessionId)
324
- : { confirmed: false, errorMessage: 'Checkout session is missing.' };
354
+ const preConfirmationSummary = await loadAccountProfileSummary({ force: true });
355
+ if (cancelled)
356
+ return;
357
+ let confirmation = querySessionId
358
+ ? { confirmed: false, errorMessage: 'Unable to confirm checkout completion.', retryable: true }
359
+ : { confirmed: false, errorMessage: 'Checkout session is missing.', retryable: false };
360
+ for (const delayMs of CHECKOUT_CONFIRM_RETRY_DELAYS_MS) {
361
+ await waitForRetry(delayMs);
362
+ if (cancelled)
363
+ return;
364
+ confirmation = querySessionId
365
+ ? await confirmCheckoutSession(querySessionId, {
366
+ autoContinueOnActivation: shouldAutoContinueAfterActivation(preConfirmationSummary, gateState)
367
+ })
368
+ : { confirmed: false, errorMessage: 'Checkout session is missing.', retryable: false };
369
+ if (cancelled)
370
+ return;
371
+ if (confirmation.confirmed) {
372
+ setFeedback({ type: 'success', message: 'Checkout completed successfully.' });
373
+ scheduleTransientQueryClear();
374
+ return;
375
+ }
376
+ if (!confirmation.retryable) {
377
+ break;
378
+ }
379
+ }
380
+ if (cancelled)
381
+ return;
325
382
  if (!confirmation.confirmed) {
326
383
  const summary = await loadAccountProfileSummary({ force: true });
384
+ if (cancelled)
385
+ return;
327
386
  setFeedback({
328
387
  type: 'error',
329
388
  message: resolveRecoveryMessage(summary, confirmation.errorMessage)
@@ -331,10 +390,12 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
331
390
  scheduleTransientQueryClear();
332
391
  return;
333
392
  }
334
- setFeedback({ type: 'success', message: 'Checkout completed successfully.' });
335
- scheduleTransientQueryClear();
336
393
  })();
337
- return;
394
+ return () => {
395
+ cancelled = true;
396
+ pendingRetryTimeouts.forEach((timeoutId) => window.clearTimeout(timeoutId));
397
+ pendingRetryTimeouts.clear();
398
+ };
338
399
  }
339
400
  if (checkoutState === 'cancel') {
340
401
  void (async () => {
@@ -349,29 +410,31 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
349
410
  }
350
411
  if ((checkoutState === 'start' || checkoutState === 'free') && isAuthenticated && queryPlanVersionId && !checkoutStartRan.current) {
351
412
  checkoutStartRan.current = true;
352
- void startCheckout(queryPlanVersionId, queryInterval);
413
+ void (async () => {
414
+ const preCheckoutSummary = await loadAccountProfileSummary({ force: true });
415
+ await startCheckout(queryPlanVersionId, queryInterval, {
416
+ autoContinueOnActivation: shouldAutoContinueAfterActivation(preCheckoutSummary, gateState)
417
+ });
418
+ })();
419
+ return;
420
+ }
421
+ }, [authSessionResolved, checkoutState, confirmCheckoutSession, gateState, isAuthenticated, loadAccountProfileSummary, queryInterval, queryPlanVersionId, querySessionId, resolveRecoveryMessage, scheduleTransientQueryClear, shouldAutoContinueAfterActivation, startCheckout]);
422
+ useEffect(() => {
423
+ if (checkoutState === 'success' || checkoutState === 'cancel' || checkoutState === 'start' || checkoutState === 'free') {
353
424
  return;
354
425
  }
355
426
  if (gateMessage) {
356
- setFeedback({ type: 'error', message: gateMessage });
427
+ setFeedback({ type: lifecyclePresentation.statusTone, message: gateMessage });
357
428
  return;
358
429
  }
359
430
  setFeedback((current) => {
360
431
  if (!current || current.type !== 'error')
361
432
  return current;
362
- if (current.message === GATE_MESSAGES.planSelectionRequired)
363
- return null;
364
- if (current.message === GATE_MESSAGES.misconfigured)
365
- return null;
366
- if (current.message === GATE_MESSAGES.missingEntitlement)
367
- return null;
368
- if (current.message === BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_EXPIRED)
369
- return null;
370
- if (current.message === BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_SUPERSEDED)
433
+ if (current.message === lifecyclePresentation.message)
371
434
  return null;
372
435
  return current;
373
436
  });
374
- }, [checkoutState, confirmCheckoutSession, gateMessage, isAuthenticated, loadAccountProfileSummary, queryInterval, queryPlanVersionId, querySessionId, resolveRecoveryMessage, scheduleTransientQueryClear, startCheckout]);
437
+ }, [checkoutState, gateMessage, lifecyclePresentation.message, lifecyclePresentation.statusTone]);
375
438
  const handleSelectPlan = useCallback(async (intent) => {
376
439
  if (!isAuthenticated) {
377
440
  const nextCheckoutState = intent.pricingType === 'free' ? 'free' : 'start';
@@ -392,13 +455,18 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
392
455
  await openBillingPortal();
393
456
  return;
394
457
  }
458
+ const autoContinueOnActivation = shouldAutoContinueAfterActivation(accountProfileSummary, gateState);
395
459
  if (intent.pricingType === 'free') {
396
- await startCheckout(intent.planVersionId, intent.interval);
460
+ await startCheckout(intent.planVersionId, intent.interval, { autoContinueOnActivation });
397
461
  return;
398
462
  }
399
- await startCheckout(intent.planVersionId, intent.interval);
400
- }, [isAuthenticated, navigate, openBillingPortal, startCheckout]);
463
+ await startCheckout(intent.planVersionId, intent.interval, { autoContinueOnActivation });
464
+ }, [accountProfileSummary, gateState, isAuthenticated, navigate, openBillingPortal, shouldAutoContinueAfterActivation, startCheckout]);
401
465
  const handleBack = useCallback(() => {
466
+ if (hasResolvedActiveEntitlement && onContinueToTaskforce) {
467
+ void onContinueToTaskforce();
468
+ return;
469
+ }
402
470
  if (embedded) {
403
471
  closeEmbeddedPlansScreen();
404
472
  return;
@@ -408,8 +476,9 @@ export function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessio
408
476
  return;
409
477
  }
410
478
  navigate('/');
411
- }, [closeEmbeddedPlansScreen, embedded, navigate]);
412
- const topActions = embedded ? (_jsx(_Fragment, { children: isAuthenticated && accountProfileSummary?.stripeCustomerId && (_jsx("button", { className: styles.navAction, onClick: () => { void openBillingPortal(); }, disabled: actionBusy, children: "Manage billing" })) })) : (_jsxs(_Fragment, { children: [_jsx("button", { className: styles.navAction, onClick: handleBack, children: "Back" }), isAuthenticated && accountProfileSummary?.stripeCustomerId && (_jsx("button", { className: styles.navAction, onClick: () => { void openBillingPortal(); }, disabled: actionBusy, children: "Manage billing" })), !isAuthenticated && (_jsxs("button", { className: styles.navAction, onClick: () => navigate(`/login?mode=login&next=${encodeURIComponent(buildEmbeddedPlansPath())}`), children: [_jsx(User, { size: 16, style: { marginRight: '8px', verticalAlign: 'text-bottom' } }), "Sign In"] }))] }));
479
+ }, [closeEmbeddedPlansScreen, embedded, hasResolvedActiveEntitlement, navigate, onContinueToTaskforce]);
480
+ const continueAction = hasResolvedActiveEntitlement && onContinueToTaskforce ? (_jsx("button", { className: styles.navAction, onClick: () => { void onContinueToTaskforce(); }, disabled: continueBusy || actionBusy, children: "Take Me to Taskforce" })) : null;
481
+ const topActions = embedded ? (_jsxs(_Fragment, { children: [continueAction, isAuthenticated && accountProfileSummary?.stripeCustomerId && (_jsx("button", { className: styles.navAction, onClick: () => { void openBillingPortal(); }, disabled: actionBusy, children: "Manage billing" }))] })) : (_jsxs(_Fragment, { children: [_jsx("button", { className: styles.navAction, onClick: handleBack, disabled: continueBusy || (isAuthenticated && !hasResolvedActiveEntitlement), children: hasResolvedActiveEntitlement ? 'Take Me to Taskforce' : 'Back' }), isAuthenticated && accountProfileSummary?.stripeCustomerId && (_jsx("button", { className: styles.navAction, onClick: () => { void openBillingPortal(); }, disabled: actionBusy, children: "Manage billing" })), !isAuthenticated && (_jsxs("button", { className: styles.navAction, onClick: () => navigate(`/login?mode=login&next=${encodeURIComponent(buildEmbeddedPlansPath())}`), children: [_jsx(User, { size: 16, style: { marginRight: '8px', verticalAlign: 'text-bottom' } }), "Sign In"] }))] }));
413
482
  const chrome = embedded ? null : (_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode === 'cloud' ? 'cloud' : 'local', theme: currentTheme, actions: topActions }));
414
- return (_jsx(PlanComparisonPage, { chrome: chrome, heading: "Subscription Plans", subtitle: "Manage your subscription and explore available features. Your current plan is highlighted below.", isAuthenticated: isAuthenticated, authSessionResolved: authSessionResolved, currentPlanId: String(accountProfileSummary?.planId || queryPlanId || '').trim() || null, currentPlanVersionId: String(accountProfileSummary?.planVersionId || queryPlanVersionId || '').trim() || null, currentEntitlementState: String(accountProfileSummary?.entitlementState || '').trim() || null, canManageCurrentPlan: Boolean(accountProfileSummary?.stripeCustomerId), pricingEndpoint: pricingEndpoints[0], statusBanner: feedback, onBack: handleBack, backLabel: embedded ? 'Back to Board' : 'Back to App', topActions: embedded ? topActions : null, shellOwnsScroll: shellOwnsScroll, showPageBackButton: !embedded, fallbackPricingSource: fallbackPricingSource, onSelectPlan: handleSelectPlan }));
483
+ return (_jsx(PlanComparisonPage, { chrome: chrome, heading: "Subscription Plans", subtitle: "Manage your subscription and explore available features. Your current plan is highlighted below.", isAuthenticated: isAuthenticated, authSessionResolved: authSessionResolved, currentPlanId: String(accountProfileSummary?.planId || queryPlanId || '').trim() || null, currentPlanVersionId: String(accountProfileSummary?.planVersionId || queryPlanVersionId || '').trim() || null, currentEntitlementState: String(accountProfileSummary?.entitlementState || '').trim() || null, canManageCurrentPlan: Boolean(accountProfileSummary?.stripeCustomerId), pricingEndpoint: pricingEndpoints[0], statusBanner: feedback, onBack: handleBack, backLabel: hasResolvedActiveEntitlement ? 'Take Me to Taskforce' : (embedded ? 'Back to Board' : 'Back to App'), backDisabled: continueBusy || (isAuthenticated && !hasResolvedActiveEntitlement), topActions: embedded ? topActions : null, shellOwnsScroll: shellOwnsScroll, showPageBackButton: !embedded, fallbackPricingSource: fallbackPricingSource, onSelectPlan: handleSelectPlan }));
415
484
  }