@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
@@ -35,9 +35,10 @@ import { getLocale, t } from '../../localization';
35
35
  import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
36
36
  import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
37
37
  import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
38
+ import { resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
38
39
  import { AppShellHeader } from './AppShellHeader';
39
40
  import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
40
- import { logBootstrapDebug } from '../../hooks/workspace/workspaceLocalState';
41
+ import { logBootstrapDebug, persistRemoteUiState } from '../../hooks/workspace/workspaceLocalState';
41
42
  import { FilterToolbar } from './panels/FilterToolbar';
42
43
  import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
43
44
  import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
@@ -192,9 +193,6 @@ export function StandaloneLayout(props) {
192
193
  const nextQuery = params.toString();
193
194
  navigate(`${location.pathname === '/' ? '/' : location.pathname}${nextQuery ? `?${nextQuery}` : ''}${location.hash || ''}`);
194
195
  }, [location.hash, location.pathname, location.search, navigate]);
195
- const handlePlansBack = useCallback(() => {
196
- closePlansScreen();
197
- }, [closePlansScreen]);
198
196
  const {
199
197
  // State
200
198
  activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, activeWorkspaceModule, setActiveWorkspaceModule, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus,
@@ -504,34 +502,31 @@ export function StandaloneLayout(props) {
504
502
  return;
505
503
  const timeoutId = window.setTimeout(async () => {
506
504
  try {
507
- await fetch('/api/taskforce/ui-state', {
508
- method: 'POST',
509
- headers: { 'Content-Type': 'application/json', ...workspaceUiStateHeaders },
510
- credentials: 'include',
511
- body: JSON.stringify({
512
- stateKey: 'layout',
513
- patch: {
514
- scheduleShowWeekends,
515
- scheduleShowBacklog,
516
- scheduleOnlyExpired,
517
- scheduleSidebarOpen,
518
- scheduleSelectedDate,
519
- scheduleCalendarMonth,
520
- scheduleScrollLeft,
521
- showFilters,
522
- documentTypeFilters,
523
- documentAttachmentFilters,
524
- planningDrawerOpen,
525
- planningTreeCollapsed,
526
- planningPrimaryCollapsed,
527
- planningSecondaryCollapsed,
528
- planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
529
- planningDrawerDetail,
530
- planningNestedWorkstreamDetailId,
531
- annotatedTarget: requestedAnnotatedTarget,
532
- annotatedSessionId: requestedAnnotatedSessionId
533
- }
534
- })
505
+ await persistRemoteUiState({
506
+ stateKey: 'layout',
507
+ workspaceId: currentWorkspaceId,
508
+ headers: workspaceUiStateHeaders,
509
+ patch: {
510
+ scheduleShowWeekends,
511
+ scheduleShowBacklog,
512
+ scheduleOnlyExpired,
513
+ scheduleSidebarOpen,
514
+ scheduleSelectedDate,
515
+ scheduleCalendarMonth,
516
+ scheduleScrollLeft,
517
+ showFilters,
518
+ documentTypeFilters,
519
+ documentAttachmentFilters,
520
+ planningDrawerOpen,
521
+ planningTreeCollapsed,
522
+ planningPrimaryCollapsed,
523
+ planningSecondaryCollapsed,
524
+ planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
525
+ planningDrawerDetail,
526
+ planningNestedWorkstreamDetailId,
527
+ annotatedTarget: requestedAnnotatedTarget,
528
+ annotatedSessionId: requestedAnnotatedSessionId
529
+ }
535
530
  });
536
531
  }
537
532
  catch {
@@ -828,8 +823,13 @@ export function StandaloneLayout(props) {
828
823
  assigneeOptions.forEach((option) => {
829
824
  map.set(String(option.value), option.label);
830
825
  });
826
+ const normalizedAuthUserId = String(authUserId || '').trim();
827
+ const normalizedAuthIdentityLabel = String(authUserDisplayName || authUserEmail || '').trim();
828
+ if (normalizedAuthUserId && normalizedAuthIdentityLabel) {
829
+ map.set(normalizedAuthUserId, normalizedAuthIdentityLabel);
830
+ }
831
831
  return map;
832
- }, [assigneeOptions]);
832
+ }, [assigneeOptions, authUserDisplayName, authUserEmail, authUserId]);
833
833
  const planningStructure = useMemo(() => {
834
834
  const planningScopedTasks = taskScope === 'archived'
835
835
  ? archivedScopeAllTasks
@@ -928,28 +928,36 @@ export function StandaloneLayout(props) {
928
928
  const referenceLabel = formatInitiativeReference(initiative);
929
929
  return {
930
930
  value: initiative.id,
931
- label: referenceLabel ? `${referenceLabel} - ${initiative.title}` : initiative.title,
932
- selectedLabel: referenceLabel || initiative.title,
931
+ label: referenceLabel || initiative.title,
933
932
  };
934
933
  }), [planningStructure.initiatives]);
935
- const workstreamFilterOptions = useMemo(() => [
936
- ...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => {
937
- const referenceLabel = formatWorkstreamReference(workstream);
938
- return {
939
- value: workstream.id,
940
- label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
941
- selectedLabel: referenceLabel || workstream.title,
942
- };
943
- })),
944
- ...planningStructure.standaloneWorkstreams.map((workstream) => {
934
+ const workstreamFilterOptions = useMemo(() => {
935
+ const visibleInitiativeWorkstreams = selectedPlanningInitiativeId
936
+ ? (planningStructure.initiativeById.get(selectedPlanningInitiativeId)?.workstreams || [])
937
+ : planningStructure.initiatives.flatMap((initiative) => initiative.workstreams);
938
+ const initiativeWorkstreams = visibleInitiativeWorkstreams.map((workstream) => {
945
939
  const referenceLabel = formatWorkstreamReference(workstream);
946
940
  return {
947
941
  value: workstream.id,
948
- label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
949
- selectedLabel: referenceLabel || workstream.title,
942
+ label: referenceLabel || workstream.title,
950
943
  };
951
- }),
952
- ], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
944
+ });
945
+ const standaloneWorkstreams = selectedPlanningInitiativeId
946
+ ? []
947
+ : planningStructure.standaloneWorkstreams.map((workstream) => {
948
+ const referenceLabel = formatWorkstreamReference(workstream);
949
+ return {
950
+ value: workstream.id,
951
+ label: referenceLabel || workstream.title,
952
+ };
953
+ });
954
+ return [...initiativeWorkstreams, ...standaloneWorkstreams];
955
+ }, [
956
+ planningStructure.initiativeById,
957
+ planningStructure.initiatives,
958
+ planningStructure.standaloneWorkstreams,
959
+ selectedPlanningInitiativeId,
960
+ ]);
953
961
  const selectedPlanningTaskIds = useMemo(() => {
954
962
  if (selectedPlanningWorkstreamId) {
955
963
  return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
@@ -1229,6 +1237,7 @@ export function StandaloneLayout(props) {
1229
1237
  const [billingError, setBillingError] = useState(null);
1230
1238
  const [billingActionBusy, setBillingActionBusy] = useState(false);
1231
1239
  const [billingActionError, setBillingActionError] = useState(null);
1240
+ const [plansNavigationBusy, setPlansNavigationBusy] = useState(false);
1232
1241
  const [billingNotice, setBillingNotice] = useState(null);
1233
1242
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1234
1243
  const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
@@ -1543,7 +1552,7 @@ export function StandaloneLayout(props) {
1543
1552
  });
1544
1553
  accountSurfaceOpenStartedAtRef.current = null;
1545
1554
  }, [accountProfileSummary?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
1546
- const loadAccountProfileSummary = useCallback(async () => {
1555
+ const loadAccountProfileSummary = useCallback(async (options) => {
1547
1556
  const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);
1548
1557
  if (!isAuthenticated) {
1549
1558
  setAccountProfileSummary(null);
@@ -1563,7 +1572,10 @@ export function StandaloneLayout(props) {
1563
1572
  setTeamManagementAccessLoading(canAccessCloudTeamManagement);
1564
1573
  setTeamManagementAccessError(null);
1565
1574
  try {
1566
- const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, { identityKey: authUserId });
1575
+ const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, {
1576
+ identityKey: authUserId,
1577
+ force: options?.force === true
1578
+ });
1567
1579
  const nextRole = payload?.workspaceRole === 'owner'
1568
1580
  || payload?.workspaceRole === 'admin'
1569
1581
  || payload?.workspaceRole === 'member'
@@ -1611,6 +1623,23 @@ export function StandaloneLayout(props) {
1611
1623
  markAccountSurfaceLoadSettled('teamManagement');
1612
1624
  }
1613
1625
  }, [authUserId, canAccessCloudTeamManagement, isAuthenticated, markAccountSurfaceLoadSettled, resolveCloudAuthUrl]);
1626
+ const applyPlansAccountProfileSummary = useCallback((payload) => {
1627
+ setAccountProfileSummary(payload);
1628
+ const nextRole = payload?.workspaceRole === 'owner'
1629
+ || payload?.workspaceRole === 'admin'
1630
+ || payload?.workspaceRole === 'member'
1631
+ || payload?.workspaceRole === 'read-only'
1632
+ ? payload.workspaceRole
1633
+ : null;
1634
+ const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
1635
+ ? payload.teamPlanMode
1636
+ : 'unknown';
1637
+ setTeamWorkspaceRole(nextRole);
1638
+ setTeamPlanMode(nextMode);
1639
+ }, []);
1640
+ const hasActiveCommercialEntitlement = useMemo(() => {
1641
+ return resolveCommercialLifecyclePresentation(accountProfileSummary).allowReturnToApp;
1642
+ }, [accountProfileSummary]);
1614
1643
  const handleStartCheckout = useCallback(async () => {
1615
1644
  const params = new URLSearchParams();
1616
1645
  params.set('screen', 'plans');
@@ -1798,6 +1827,46 @@ export function StandaloneLayout(props) {
1798
1827
  }
1799
1828
  navigate(`/setup?${params.toString()}`, { replace: true });
1800
1829
  }, [navigate]);
1830
+ const handlePlansBack = useCallback(async () => {
1831
+ if (plansNavigationBusy)
1832
+ return;
1833
+ if (!isAuthenticated) {
1834
+ closePlansScreen();
1835
+ return;
1836
+ }
1837
+ const summary = await loadAccountProfileSummary({ force: true });
1838
+ if (!resolveCommercialLifecyclePresentation(summary).allowReturnToApp) {
1839
+ return;
1840
+ }
1841
+ setPlansNavigationBusy(true);
1842
+ try {
1843
+ const result = await props.continueAfterCommercialOnboarding();
1844
+ if (!result.success) {
1845
+ pushNotice(result.error || 'Unable to finish onboarding.', 'error');
1846
+ if (result.destination === 'login') {
1847
+ navigate('/login', { replace: true });
1848
+ }
1849
+ return;
1850
+ }
1851
+ if (result.destination === 'setup') {
1852
+ openWorkspaceSetup();
1853
+ return;
1854
+ }
1855
+ closePlansScreen();
1856
+ }
1857
+ finally {
1858
+ setPlansNavigationBusy(false);
1859
+ }
1860
+ }, [
1861
+ closePlansScreen,
1862
+ isAuthenticated,
1863
+ loadAccountProfileSummary,
1864
+ navigate,
1865
+ openWorkspaceSetup,
1866
+ plansNavigationBusy,
1867
+ props,
1868
+ pushNotice
1869
+ ]);
1801
1870
  const handleSwitchWorkspace = useCallback(async (workspaceId) => {
1802
1871
  if (!workspaceId || workspaceActionBusy)
1803
1872
  return;
@@ -2545,6 +2614,9 @@ export function StandaloneLayout(props) {
2545
2614
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
2546
2615
  setShowAccountMenu(false);
2547
2616
  await logout();
2617
+ if (runtimeMode === 'cloud') {
2618
+ navigate('/login', { replace: true });
2619
+ }
2548
2620
  }, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
2549
2621
  const cycleEmptyColumnVisibility = useCallback(() => {
2550
2622
  setEmptyColumnMode((prev) => {
@@ -3180,7 +3252,7 @@ export function StandaloneLayout(props) {
3180
3252
  display: 'inline-flex',
3181
3253
  alignItems: 'center',
3182
3254
  justifyContent: 'center'
3183
- }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "Back" })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
3255
+ }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: () => { void handlePlansBack(); }, title: isAuthenticated && !hasActiveCommercialEntitlement ? 'Choose a plan to continue.' : 'Continue to Taskforce', disabled: plansNavigationBusy || (isAuthenticated && !hasActiveCommercialEntitlement), children: hasActiveCommercialEntitlement ? 'Take Me to Taskforce' : 'Back' })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true, onAccountProfileSummaryChange: applyPlansAccountProfileSummary, onContinueToTaskforce: handlePlansBack, continueBusy: plansNavigationBusy }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
3184
3256
  setRequestedDocPath(null);
3185
3257
  setRequestedDocAssetId(null);
3186
3258
  }, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
@@ -3240,7 +3312,16 @@ export function StandaloneLayout(props) {
3240
3312
  top: '10px',
3241
3313
  right: '10px',
3242
3314
  zIndex: 20,
3243
- }, children: _jsx(ChevronLeft, { size: 16 }) }))] }), groupBy === 'schedule' && (_jsx(ScheduleSidebar, { open: scheduleSidebarOpen, onClose: () => setScheduleSidebarOpen(false), scheduleSelectedDate: scheduleSelectedDate, setScheduleSelectedDate: setScheduleSelectedDate, scheduleCalendarMonth: scheduleCalendarMonth, setScheduleCalendarMonth: setScheduleCalendarMonth, scheduleShowWeekends: scheduleShowWeekends, setScheduleShowWeekends: setScheduleShowWeekends, scheduleShowBacklog: scheduleShowBacklog, setScheduleShowBacklog: setScheduleShowBacklog, scheduleOnlyExpired: scheduleOnlyExpired, setScheduleOnlyExpired: setScheduleOnlyExpired, expiredScheduledCount: expiredScheduledCount, overdueDueCount: overdueDueCount, expiredLeafCandidates: expiredLeafCandidates, expiredRecoveryCandidates: expiredRecoveryCandidates, onMoveExpiredToSelectedWeek: handleMoveExpiredToSelectedWeek, onMoveExpiredToBacklog: handleMoveExpiredToBacklog, scheduleBulkBusy: scheduleBulkBusy, globalWeekStartsOn: globalWeekStartsOn, resolvedLocale: resolvedLocale, todayDateOnly: todayDateOnly, scheduleBaseTasks: scheduleBaseTasks, scheduleWeekStart: scheduleWeekStart, scheduleWeekLabel: scheduleWeekLabel }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? (currentTask?.isDeleted ? 'Deleted Task' : 'Edit Task') : 'New Task', size: taskModalFullscreen ? 'full' : 'xl', theme: currentTheme, headerActions: (_jsx("button", { className: "tf-control-icon", onClick: () => setTaskModalFullscreen((value) => !value), title: taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', "aria-label": taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', children: taskModalFullscreen ? _jsx(Minimize2, { size: 18 }) : _jsx(Maximize2, { size: 18 }) })), draggable: !taskModalFullscreen, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleSetStatus: handleSetStatus, handleArchiveTask: handleArchiveTask, handleRestoreDeletedTask: currentTask?.deletedRecordId
3315
+ }, children: _jsx(ChevronLeft, { size: 16 }) }))] }), groupBy === 'schedule' && (_jsx(ScheduleSidebar, { open: scheduleSidebarOpen, onClose: () => setScheduleSidebarOpen(false), scheduleSelectedDate: scheduleSelectedDate, setScheduleSelectedDate: setScheduleSelectedDate, scheduleCalendarMonth: scheduleCalendarMonth, setScheduleCalendarMonth: setScheduleCalendarMonth, scheduleShowWeekends: scheduleShowWeekends, setScheduleShowWeekends: setScheduleShowWeekends, scheduleShowBacklog: scheduleShowBacklog, setScheduleShowBacklog: setScheduleShowBacklog, scheduleOnlyExpired: scheduleOnlyExpired, setScheduleOnlyExpired: setScheduleOnlyExpired, expiredScheduledCount: expiredScheduledCount, overdueDueCount: overdueDueCount, expiredLeafCandidates: expiredLeafCandidates, expiredRecoveryCandidates: expiredRecoveryCandidates, onMoveExpiredToSelectedWeek: handleMoveExpiredToSelectedWeek, onMoveExpiredToBacklog: handleMoveExpiredToBacklog, scheduleBulkBusy: scheduleBulkBusy, globalWeekStartsOn: globalWeekStartsOn, resolvedLocale: resolvedLocale, todayDateOnly: todayDateOnly, scheduleBaseTasks: scheduleBaseTasks, scheduleWeekStart: scheduleWeekStart, scheduleWeekLabel: scheduleWeekLabel }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? (currentTask?.isDeleted ? 'Deleted Task' : 'Edit Task') : 'New Task', size: taskModalFullscreen ? 'full' : 'xl', theme: currentTheme, headerActions: (_jsx("button", { className: "tf-control-icon", onClick: () => setTaskModalFullscreen((value) => !value), title: taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', "aria-label": taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', children: taskModalFullscreen ? _jsx(Minimize2, { size: 18 }) : _jsx(Maximize2, { size: 18 }) })), draggable: !taskModalFullscreen, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleSetStatus: handleSetStatus, handleArchiveTask: handleArchiveTask, handleUnarchiveTask: (taskId) => {
3316
+ if (currentTask?.isDeleted) {
3317
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
3318
+ if (!deletedRecord)
3319
+ return;
3320
+ void handleRestoreDeletedTask(deletedRecord.id);
3321
+ return;
3322
+ }
3323
+ void handleUnarchive(taskId);
3324
+ }, handleRestoreDeletedTask: currentTask?.deletedRecordId
3244
3325
  ? (deletedRecordId) => {
3245
3326
  void handleRestoreDeletedTask(deletedRecordId);
3246
3327
  }
@@ -3253,7 +3334,7 @@ export function StandaloneLayout(props) {
3253
3334
  resetForm();
3254
3335
  setActiveTab('tasks');
3255
3336
  }
3256
- : undefined }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
3337
+ : undefined }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, initiatives: props.initiatives, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
3257
3338
  setSettingsSection(section);
3258
3339
  setActiveTab('settings');
3259
3340
  }, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
@@ -270,7 +270,16 @@ export function WidgetView(props) {
270
270
  handleNavigation(() => {
271
271
  setActiveTab('settings');
272
272
  });
273
- }, title: "Settings", children: _jsx(Settings, { size: 18 }) }), _jsx("button", { className: "tf-control-icon", onClick: handleXClick, title: activeTab === 'tasks' ? "Close" : "Back to Tasks", children: activeTab === 'tasks' ? _jsx(X, { size: 20 }) : _jsx(ArrowLeft, { size: 20 }) })] })] }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), activeTab === 'add' && (_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleArchiveTask: handleArchiveTask, handleRestoreDeletedTask: (deletedRecordId) => {
273
+ }, title: "Settings", children: _jsx(Settings, { size: 18 }) }), _jsx("button", { className: "tf-control-icon", onClick: handleXClick, title: activeTab === 'tasks' ? "Close" : "Back to Tasks", children: activeTab === 'tasks' ? _jsx(X, { size: 20 }) : _jsx(ArrowLeft, { size: 20 }) })] })] }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), activeTab === 'add' && (_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleArchiveTask: handleArchiveTask, handleUnarchiveTask: (taskId) => {
274
+ if (currentTask?.isDeleted) {
275
+ const deletedRecord = deletedRecordByTaskId.get(taskId);
276
+ if (deletedRecord) {
277
+ void handleRestoreDeletedTask(deletedRecord.id);
278
+ }
279
+ return;
280
+ }
281
+ handleUnarchive(taskId);
282
+ }, handleRestoreDeletedTask: (deletedRecordId) => {
274
283
  void handleRestoreDeletedTask(deletedRecordId);
275
284
  }, handlePermanentlyDeleteDeletedTask: (deletedRecordId) => {
276
285
  void handlePermanentlyDeleteDeletedTask(deletedRecordId);
@@ -290,7 +299,7 @@ export function WidgetView(props) {
290
299
  void handleDelete(taskId);
291
300
  }, onDeleteAllDeleted: () => {
292
301
  void handleEmptyDeletedTasks();
293
- }, onFetchArchive: fetchArchive, onAddTaskToCategory: handleAddTaskToCategory, supplementalTasks: deletedScopeTasks })), activeTab === 'add' && (_jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: onTaxonomyChange, taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: "", descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
302
+ }, onFetchArchive: fetchArchive, onAddTaskToCategory: handleAddTaskToCategory, supplementalTasks: deletedScopeTasks })), activeTab === 'add' && (_jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: onTaxonomyChange, taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: "", descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, initiatives: props.initiatives, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
294
303
  handleNavigation(() => {
295
304
  setSettingsSection(section);
296
305
  setActiveTab('settings');
@@ -31,15 +31,17 @@ describe('FilterToolbar', () => {
31
31
  expect(screen.getByRole('button', { name: /All Initiatives/i })).toBeInTheDocument();
32
32
  expect(screen.getByRole('button', { name: /All Workstreams/i })).toBeInTheDocument();
33
33
  rerender(_jsx(FilterToolbar, { categoryFilterOptions: [], typeFilterOptions: [], priorities: [], filterCategories: [], setFilterCategories: vi.fn(), filterTypes: [], setFilterTypes: vi.fn(), filterPriorities: [], setFilterPriorities: vi.fn(), filterStatus: [], setFilterStatus: vi.fn(), filterAssignees: [], setFilterAssignees: vi.fn(), assigneeOptions: [], initiativeFilterOptions: [
34
- { value: 'initiative-1', label: 'Stability Sprint' },
34
+ { value: 'initiative-1', label: 'IN-101' },
35
35
  ], selectedInitiativeId: "", setSelectedInitiativeId: setSelectedInitiativeId, workstreamFilterOptions: [
36
- { value: 'workstream-1', label: 'Header polish' },
36
+ { value: 'workstream-1', label: 'WS-202' },
37
37
  ], selectedWorkstreamId: "", setSelectedWorkstreamId: setSelectedWorkstreamId, taskScope: "open", setTaskScope: vi.fn(), showArchive: false, setShowArchive: vi.fn(), fetchArchive: vi.fn(), clearFilters: vi.fn() }));
38
38
  await user.click(screen.getByRole('button', { name: /All Initiatives/i }));
39
- await user.click(screen.getByRole('button', { name: 'Stability Sprint' }));
39
+ expect(screen.queryByText('Stability Sprint')).not.toBeInTheDocument();
40
+ await user.click(screen.getByRole('button', { name: 'IN-101' }));
40
41
  expect(setSelectedInitiativeId).toHaveBeenCalledWith('initiative-1');
41
42
  await user.click(screen.getByRole('button', { name: /All Workstreams/i }));
42
- await user.click(screen.getByRole('button', { name: 'Header polish' }));
43
+ expect(screen.queryByText('Header polish')).not.toBeInTheDocument();
44
+ await user.click(screen.getByRole('button', { name: 'WS-202' }));
43
45
  expect(setSelectedWorkstreamId).toHaveBeenCalledWith('workstream-1');
44
46
  });
45
47
  });
@@ -1,17 +1,5 @@
1
+ import type { AccountProfileSummary } from '../../../../utils/accountProfileSummaryCache';
1
2
  import type { ModalRuntimeMode, ModalThemeName } from './types';
2
- interface AccountProfileSummaryLike {
3
- planId?: string | null;
4
- planName?: string | null;
5
- entitlementState?: string | null;
6
- stripeStatus?: string | null;
7
- effectiveUntil?: string | null;
8
- stripeSubscriptionId?: string | null;
9
- stripeCustomerId?: string | null;
10
- gate?: string | null;
11
- message?: string | null;
12
- billingConflictCode?: 'CHECKOUT_SESSION_EXPIRED' | 'CHECKOUT_SESSION_SUPERSEDED' | null;
13
- planSelectionRequired?: boolean;
14
- }
15
3
  interface AccountHubModalProps {
16
4
  isOpen: boolean;
17
5
  theme: ModalThemeName;
@@ -26,7 +14,7 @@ interface AccountHubModalProps {
26
14
  billingNotice: string | null;
27
15
  billingActionBusy: boolean;
28
16
  billingIntervalChoice: 'month' | 'year';
29
- accountProfileSummary: AccountProfileSummaryLike | null;
17
+ accountProfileSummary: AccountProfileSummary | null;
30
18
  currentWorkspaceId: string;
31
19
  canOpenTeamManagement: boolean;
32
20
  canManageWorkspaceSync: boolean;
@@ -5,21 +5,19 @@ import accountStyles from '../../StandaloneAccount.module.css';
5
5
  import authStyles from '../../AuthShell.module.css';
6
6
  import managementStyles from '../../StandaloneManagement.module.css';
7
7
  import styles from '../../../../Taskforce.module.css';
8
+ import { resolveCommercialLifecyclePresentation } from '../../../../utils/commercialLifecyclePresentation';
8
9
  export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi, isAuthenticated, hasAuthIdentity, authIdentityLabel, billingLoading, billingError, billingActionError, billingNotice, billingActionBusy, billingIntervalChoice, accountProfileSummary, currentWorkspaceId, canOpenTeamManagement, canManageWorkspaceSync, workspaceCloudSyncEnabled, syncStatusLabel, workspaceSyncError, syncControlBusy, onClose, onOpenWorkspaceAudit, onBillingIntervalChange, onRefreshBilling, onUpdateInterval, onManageBilling, onStartCheckout, onToggleWorkspaceSync, onOpenHelp, }) {
9
- const billingGate = String(accountProfileSummary?.gate || '').trim().toLowerCase();
10
- const billingConflictCode = String(accountProfileSummary?.billingConflictCode || '').trim().toUpperCase();
11
- const billingRecoveryMessage = String(accountProfileSummary?.message || '').trim()
12
- || (billingConflictCode === 'CHECKOUT_SESSION_EXPIRED'
13
- ? 'Your latest checkout expired. Choose a plan to continue.'
14
- : (billingConflictCode === 'CHECKOUT_SESSION_SUPERSEDED'
15
- ? 'A newer checkout is already in progress for this account. Open plans to continue.'
16
- : ''));
17
- const startCheckoutLabel = billingConflictCode === 'CHECKOUT_SESSION_EXPIRED'
18
- || accountProfileSummary?.planSelectionRequired === true
19
- || billingGate === 'plan_selection_required'
20
- ? 'Choose Plan'
21
- : (billingGate === 'checkout_pending' ? 'Open Plans' : 'Start Checkout');
10
+ const lifecyclePresentation = resolveCommercialLifecyclePresentation(accountProfileSummary);
11
+ const billingRecoveryMessage = lifecyclePresentation.message;
12
+ const startCheckoutLabel = lifecyclePresentation.primaryLabel || 'Start Checkout';
22
13
  const resolvedPlanLabel = String(accountProfileSummary?.planName || accountProfileSummary?.planId || 'n/a').trim() || 'n/a';
14
+ const primaryBillingAction = () => {
15
+ if (lifecyclePresentation.primaryAction === 'manage_billing') {
16
+ onManageBilling();
17
+ return;
18
+ }
19
+ onStartCheckout();
20
+ };
23
21
  return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Account Hub", size: "md", theme: theme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { className: managementStyles.sectionText, children: runtimeMode === 'cloud'
24
22
  ? (authRequiredForApi
25
23
  ? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
@@ -28,7 +26,7 @@ export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi
28
26
  ? (hasAuthIdentity
29
27
  ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
30
28
  : 'Signed in')
31
- : 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), billingRecoveryMessage && (_jsx("p", { className: managementStyles.sectionText, children: billingRecoveryMessage })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: resolvedPlanLabel }), ' · ', "Entitlement: ", _jsx("code", { children: String(accountProfileSummary?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(accountProfileSummary?.stripeStatus || 'n/a') }), accountProfileSummary?.effectiveUntil ? ` · Effective until ${new Date(accountProfileSummary.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => onBillingIntervalChange(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: onRefreshBilling, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: onUpdateInterval, disabled: billingActionBusy || !accountProfileSummary?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: onManageBilling, disabled: billingActionBusy || !accountProfileSummary?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: onStartCheckout, disabled: billingActionBusy, children: billingActionBusy ? 'Working...' : startCheckoutLabel })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: onOpenWorkspaceAudit, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
29
+ : 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), billingRecoveryMessage && (_jsx("p", { className: managementStyles.sectionText, children: billingRecoveryMessage })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: resolvedPlanLabel }), ' · ', "Entitlement: ", _jsx("code", { children: String(accountProfileSummary?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(accountProfileSummary?.stripeStatus || 'n/a') }), accountProfileSummary?.effectiveUntil ? ` · Effective until ${new Date(accountProfileSummary.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => onBillingIntervalChange(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: onRefreshBilling, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: onUpdateInterval, disabled: billingActionBusy || !accountProfileSummary?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: onManageBilling, disabled: billingActionBusy || !accountProfileSummary?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: primaryBillingAction, disabled: billingActionBusy || (lifecyclePresentation.primaryAction === 'manage_billing' && !accountProfileSummary?.stripeCustomerId), children: billingActionBusy ? 'Working...' : startCheckoutLabel })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: onOpenWorkspaceAudit, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
32
30
  ? (workspaceCloudSyncEnabled
33
31
  ? `Enabled · ${syncStatusLabel}`
34
32
  : 'Disabled')
@@ -17,10 +17,33 @@ describe('AccountHubModal', () => {
17
17
  billingConflictCode: 'CHECKOUT_SESSION_EXPIRED',
18
18
  planSelectionRequired: true
19
19
  }, currentWorkspaceId: "workspace-1", canOpenTeamManagement: false, canManageWorkspaceSync: false, workspaceCloudSyncEnabled: false, syncStatusLabel: "Disabled", workspaceSyncError: null, syncControlBusy: false, onClose: () => { }, onOpenWorkspaceAudit: () => { }, onBillingIntervalChange: () => { }, onRefreshBilling: () => { }, onUpdateInterval: () => { }, onManageBilling: () => { }, onStartCheckout: onStartCheckout, onToggleWorkspaceSync: () => { }, onOpenHelp: () => { } }));
20
- expect(screen.getByText('Your latest checkout expired. Choose a plan to continue.')).toBeInTheDocument();
20
+ expect(screen.getByText('Your previous checkout expired. Choose a plan to continue.')).toBeInTheDocument();
21
21
  expect(screen.getByText('Pro Plan')).toBeInTheDocument();
22
22
  const cta = screen.getByRole('button', { name: 'Choose Plan' });
23
23
  fireEvent.click(cta);
24
24
  expect(onStartCheckout).toHaveBeenCalledTimes(1);
25
25
  });
26
+ it('makes billing recovery the primary action for suspended accounts', () => {
27
+ const onManageBilling = vi.fn();
28
+ render(_jsx(AccountHubModal, { isOpen: true, theme: "dark", runtimeMode: "cloud", authRequiredForApi: true, isAuthenticated: true, hasAuthIdentity: true, authIdentityLabel: "owner@example.com", billingLoading: false, billingError: null, billingActionError: null, billingNotice: null, billingActionBusy: false, billingIntervalChoice: "month", accountProfileSummary: {
29
+ planId: 'pro',
30
+ planName: 'Pro Plan',
31
+ entitlementState: 'suspended',
32
+ stripeCustomerId: 'cus_123'
33
+ }, currentWorkspaceId: "workspace-1", canOpenTeamManagement: false, canManageWorkspaceSync: false, workspaceCloudSyncEnabled: false, syncStatusLabel: "Disabled", workspaceSyncError: null, syncControlBusy: false, onClose: () => { }, onOpenWorkspaceAudit: () => { }, onBillingIntervalChange: () => { }, onRefreshBilling: () => { }, onUpdateInterval: () => { }, onManageBilling: onManageBilling, onStartCheckout: () => { }, onToggleWorkspaceSync: () => { }, onOpenHelp: () => { } }));
34
+ expect(screen.getByText('Your access is suspended because billing needs attention. Update billing to restore access.')).toBeInTheDocument();
35
+ fireEvent.click(screen.getAllByRole('button', { name: 'Manage Billing' })[1]);
36
+ expect(onManageBilling).toHaveBeenCalledTimes(1);
37
+ });
38
+ it('shows trialing accounts as active without blocked recovery copy', () => {
39
+ render(_jsx(AccountHubModal, { isOpen: true, theme: "dark", runtimeMode: "cloud", authRequiredForApi: true, isAuthenticated: true, hasAuthIdentity: true, authIdentityLabel: "owner@example.com", billingLoading: false, billingError: null, billingActionError: null, billingNotice: null, billingActionBusy: false, billingIntervalChoice: "month", accountProfileSummary: {
40
+ planId: 'starter',
41
+ planName: 'Starter',
42
+ entitlementState: 'trialing',
43
+ stripeCustomerId: 'cus_123',
44
+ trialEnd: '2026-05-01T12:00:00.000Z'
45
+ }, currentWorkspaceId: "workspace-1", canOpenTeamManagement: false, canManageWorkspaceSync: false, workspaceCloudSyncEnabled: false, syncStatusLabel: "Disabled", workspaceSyncError: null, syncControlBusy: false, onClose: () => { }, onOpenWorkspaceAudit: () => { }, onBillingIntervalChange: () => { }, onRefreshBilling: () => { }, onUpdateInterval: () => { }, onManageBilling: () => { }, onStartCheckout: () => { }, onToggleWorkspaceSync: () => { }, onOpenHelp: () => { } }));
46
+ expect(screen.getByText(/Your trial is active through/)).toBeInTheDocument();
47
+ expect(screen.queryByText('Your access is suspended because billing needs attention. Update billing to restore access.')).not.toBeInTheDocument();
48
+ });
26
49
  });
@@ -5,7 +5,7 @@ import modalStyles from '../../../ui/Modal.module.css';
5
5
  import syncStatusStyles from '../../SyncStatusModal.module.css';
6
6
  import styles from '../../../../Taskforce.module.css';
7
7
  export function SyncStatusModal({ isOpen, theme, currentWorkspaceLabel, syncStatusMeta, workspaceCloudSyncEnabled, syncControlBusy, canManageWorkspaceSync, workspaceSyncSummary, workspaceSyncRepairBusy, referenceMismatchCount, syncStageLabel, workspaceSyncPendingChanges, formattedLastSyncTime, formattedLastPullTime, formattedLastPushTime, syncLastError, workspaceSyncDiagnostics, activeReferenceMismatchSummaries, syncDiagnosticsSummary, syncEventRows, syncEventsListRef, workspaceSyncRepairQueued, workspaceSyncBusy, workspaceSyncCopied, runtimeMode, isAuthenticated, onClose, onToggleWorkspaceSync, onRepairSync, onCopyReport, onOpenLogin, onRetrySync, }) {
8
- return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Sync Manager", size: "md", theme: theme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${syncStatusStyles.syncStatusModal}`, style: { gap: '10px' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusTop, children: [_jsx("div", { className: syncStatusStyles.syncStatusHeaderInfo, children: _jsxs("div", { className: syncStatusStyles.syncStatusMetaHeader, children: [_jsx("div", { className: syncStatusStyles.syncStatusBadge, style: {
8
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Sync Manager", size: "mdWide", theme: theme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${syncStatusStyles.syncStatusModal}`, style: { gap: '10px' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusTop, children: [_jsx("div", { className: syncStatusStyles.syncStatusHeaderInfo, children: _jsxs("div", { className: syncStatusStyles.syncStatusMetaHeader, children: [_jsx("div", { className: syncStatusStyles.syncStatusBadge, style: {
9
9
  borderColor: syncStatusMeta.border,
10
10
  background: syncStatusMeta.background,
11
11
  color: syncStatusMeta.color
@@ -26,7 +26,7 @@ const SPECS = [
26
26
  { key: 'TASKFORCE_BOOTSTRAP_ADMIN_EMAIL', description: 'Bootstrap admin email used by login fallback.', category: 'Auth', targets: ['server', 'all'] },
27
27
  { key: 'TASKFORCE_BOOTSTRAP_ADMIN_PASSWORD', description: 'Bootstrap admin password used by login fallback.', category: 'Auth', secret: true, targets: ['server', 'all'] },
28
28
  { key: 'TASKFORCE_AUTH_PUBLIC_BASE_URL', description: 'Public auth URL used in email links.', category: 'Auth', example: 'https://performance-app.taskforcehq.ai', targets: ['server', 'all'] },
29
- { key: 'TASKFORCE_AUTH_EXPOSE_DEV_TOKENS', description: 'Expose dev auth tokens in responses.', category: 'Auth', defaultValue: 'false', targets: ['server', 'all'] },
29
+ { key: 'TASKFORCE_AUTH_EXPOSE_DEV_TOKENS', description: 'Explicitly expose auth tokens in non-production responses for test automation.', category: 'Auth', defaultValue: 'false', targets: ['server', 'all'] },
30
30
  { key: 'TASKFORCE_CLOUD_PROXY_BASE_URL', description: 'Optional upstream cloud base URL for local proxying of auth/sync API calls.', category: 'Auth', example: 'https://performance-app.taskforcehq.ai', targets: ['server', 'all'] },
31
31
  { key: 'TASKFORCE_CF_ACCESS_CLIENT_ID', description: 'Optional Cloudflare Access service token client id for local cloud proxy.', category: 'Cloudflare', secret: true, targets: ['server', 'all'] },
32
32
  { key: 'TASKFORCE_CF_ACCESS_CLIENT_SECRET', description: 'Optional Cloudflare Access service token client secret for local cloud proxy.', category: 'Cloudflare', secret: true, targets: ['server', 'all'] },
@@ -50,7 +50,12 @@ export interface AiProfileServiceDeps {
50
50
  aiProfileRowToRecord: (row: Record<string, unknown>) => AiProfileRecordLike;
51
51
  generateEntityId: (prefix: string) => string;
52
52
  issueProfileToken: () => string;
53
- createRuleError: (message: string, statusCode?: number, code?: string) => Error;
53
+ countAiProfileUsage: (workspaceId: string) => {
54
+ used: number;
55
+ limit: number | null;
56
+ remaining: number | null;
57
+ };
58
+ createRuleError: (message: string, statusCode?: number, code?: string, details?: unknown) => Error;
54
59
  }
55
60
  export declare function listAiProfilesService(deps: AiProfileServiceDeps, workspaceId: string): AiProfileRecordLike[];
56
61
  export declare function getAiProfileByTokenService(deps: AiProfileServiceDeps, input: {