@taskforcehq/taskforce 0.3.303 → 0.3.304

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 (165) hide show
  1. package/dist/Taskforce.module.css +102 -5
  2. package/dist/TaskforceCore.js +514 -212
  3. package/dist/TaskforceCore.test.js +1172 -133
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  21. package/dist/components/task/TaskStatusActions.js +8 -3
  22. package/dist/components/task/TaskStatusActions.test.js +9 -0
  23. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  24. package/dist/components/task/taskKanbanDropRules.js +187 -0
  25. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  26. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  27. package/dist/components/ui/Modal.d.ts +1 -1
  28. package/dist/components/ui/Modal.js +1 -0
  29. package/dist/components/views/AppShellHeader.js +5 -1
  30. package/dist/components/views/AppShellHeader.test.js +4 -0
  31. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  32. package/dist/components/views/PlanComparisonPage.js +4 -2
  33. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  34. package/dist/components/views/PlansPage.d.ts +5 -1
  35. package/dist/components/views/PlansPage.js +39 -12
  36. package/dist/components/views/PlansPage.test.js +59 -2
  37. package/dist/components/views/StandaloneLayout.js +113 -38
  38. package/dist/components/views/WidgetView.js +11 -2
  39. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  40. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  41. package/dist/core/PlanEntitlementService.d.ts +3 -1
  42. package/dist/core/PlanEntitlementService.js +61 -11
  43. package/dist/core/SignupMonetizationSettings.test.js +37 -2
  44. package/dist/core/TaskActivityService.d.ts +4 -1
  45. package/dist/core/TaskActivityService.js +45 -0
  46. package/dist/core/TaskAuditTimeline.test.js +52 -1
  47. package/dist/core/Taskforce.d.ts +15 -11
  48. package/dist/core/Taskforce.js +43 -2
  49. package/dist/core/WorkspacePlanMode.test.js +65 -0
  50. package/dist/documentReviews/service.d.ts +1 -1
  51. package/dist/documentReviews/types.d.ts +1 -1
  52. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  53. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  54. package/dist/hooks/sync/auth.d.ts +37 -0
  55. package/dist/hooks/sync/auth.js +61 -0
  56. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  57. package/dist/hooks/sync/bootstrap.js +52 -0
  58. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  59. package/dist/hooks/sync/orchestratorShared.js +209 -0
  60. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  61. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  62. package/dist/hooks/sync/realtime.d.ts +25 -0
  63. package/dist/hooks/sync/realtime.js +60 -0
  64. package/dist/hooks/sync/recovery.d.ts +69 -0
  65. package/dist/hooks/sync/recovery.js +118 -0
  66. package/dist/hooks/sync/transfers.d.ts +122 -0
  67. package/dist/hooks/sync/transfers.js +431 -0
  68. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  69. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  70. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  71. package/dist/hooks/ui/useResourceExport.js +5 -2
  72. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  73. package/dist/hooks/useRealtimeSync.js +2 -1
  74. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  75. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  76. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  77. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  78. package/dist/hooks/useSyncOrchestrator.js +314 -873
  79. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
  80. package/dist/hooks/useTaskMutations.js +45 -6
  81. package/dist/hooks/useTaskMutations.test.js +147 -0
  82. package/dist/hooks/useTaskforce.d.ts +8 -2
  83. package/dist/hooks/useTaskforce.js +285 -812
  84. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  85. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  86. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  87. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  88. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  89. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  90. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  91. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  92. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  93. package/dist/localization/locales/en-US.js +2 -2
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  95. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  96. package/dist/mcp/clientIntegrations.d.ts +12 -1
  97. package/dist/mcp/clientIntegrations.js +161 -6
  98. package/dist/mcp/clientIntegrations.test.js +76 -4
  99. package/dist/mcp/documentHelpers.d.ts +100 -0
  100. package/dist/mcp/documentHelpers.js +161 -0
  101. package/dist/mcp/runtime.js +174 -476
  102. package/dist/mcp/runtime.test.js +81 -8
  103. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  104. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  105. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  106. package/dist/mcp/taskAttachmentTypes.js +1 -0
  107. package/dist/plugins/vite.js +1 -1
  108. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  109. package/dist/runtime/appGateDefinitions.js +1 -1
  110. package/dist/server/index.d.ts +1 -0
  111. package/dist/server/index.js +46 -3
  112. package/dist/server/index.test.js +12 -1
  113. package/dist/server/routes/admin.js +13 -19
  114. package/dist/server/routes/auth.js +37 -2
  115. package/dist/server/routes/billing.js +18 -67
  116. package/dist/server/routes/billing.test.js +43 -0
  117. package/dist/server/routes/shared.d.ts +30 -0
  118. package/dist/server/routes/shared.js +26 -0
  119. package/dist/server/routes/sync.integration.test.js +20 -0
  120. package/dist/server/routes/sync.js +110 -237
  121. package/dist/server/routes/tasks.js +49 -7
  122. package/dist/server/routes.js +8 -19
  123. package/dist/server/routes.test.js +243 -5
  124. package/dist/shared/taskActor.d.ts +10 -0
  125. package/dist/shared/taskActor.js +1 -0
  126. package/dist/sync/collaborationSyncPayload.js +4 -0
  127. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  128. package/dist/sync/collaborationSyncState.d.ts +3 -1
  129. package/dist/sync/collaborationSyncState.js +19 -0
  130. package/dist/sync/contentSyncState.js +12 -10
  131. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  132. package/dist/sync/syncApplyHandlers.js +370 -8
  133. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  134. package/dist/sync/syncApplyHandlers.test.js +377 -0
  135. package/dist/sync/syncService.d.ts +8 -1
  136. package/dist/sync/syncService.js +36 -2
  137. package/dist/sync/syncService.test.js +48 -0
  138. package/dist/sync/workspacePullFeed.d.ts +1 -0
  139. package/dist/sync/workspacePullFeed.js +66 -59
  140. package/dist/sync/workspacePullFeed.test.js +72 -4
  141. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  142. package/dist/sync/workspaceSyncModel.js +65 -0
  143. package/dist/sync/workspaceSyncModel.test.js +119 -0
  144. package/dist/types.d.ts +2 -10
  145. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  146. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  147. package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  148. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
  149. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  150. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  151. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  152. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
  153. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  154. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  155. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
  156. package/dist/ui/index.html +4 -4
  157. package/dist/utils/taskActivity.js +4 -0
  158. package/dist/utils/taskActivity.test.js +25 -1
  159. package/package.json +3 -1
  160. package/scripts/playwright-auth.sh +4 -0
  161. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  162. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  163. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  164. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  165. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -37,7 +37,7 @@ import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loa
37
37
  import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
38
38
  import { AppShellHeader } from './AppShellHeader';
39
39
  import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
40
- import { logBootstrapDebug } from '../../hooks/workspace/workspaceLocalState';
40
+ import { logBootstrapDebug, persistRemoteUiState } from '../../hooks/workspace/workspaceLocalState';
41
41
  import { FilterToolbar } from './panels/FilterToolbar';
42
42
  import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
43
43
  import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
@@ -56,6 +56,7 @@ const WorkflowsModule = React.lazy(() => import('../features/WorkflowsModule').t
56
56
  const AgentsModule = React.lazy(() => import('../features/AgentsModule').then(m => ({ default: m.AgentsModule })));
57
57
  const InitiativesModule = React.lazy(() => import('../features/InitiativesModule').then(m => ({ default: m.InitiativesModule })));
58
58
  const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
59
+ const ACTIVE_COMMERCIAL_STATES = new Set(['active', 'trialing', 'grace']);
59
60
  const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
60
61
  const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
61
62
  const resolveSameOriginTaskforcePath = (path) => path;
@@ -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
@@ -1229,6 +1229,7 @@ export function StandaloneLayout(props) {
1229
1229
  const [billingError, setBillingError] = useState(null);
1230
1230
  const [billingActionBusy, setBillingActionBusy] = useState(false);
1231
1231
  const [billingActionError, setBillingActionError] = useState(null);
1232
+ const [plansNavigationBusy, setPlansNavigationBusy] = useState(false);
1232
1233
  const [billingNotice, setBillingNotice] = useState(null);
1233
1234
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1234
1235
  const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
@@ -1543,7 +1544,7 @@ export function StandaloneLayout(props) {
1543
1544
  });
1544
1545
  accountSurfaceOpenStartedAtRef.current = null;
1545
1546
  }, [accountProfileSummary?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
1546
- const loadAccountProfileSummary = useCallback(async () => {
1547
+ const loadAccountProfileSummary = useCallback(async (options) => {
1547
1548
  const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);
1548
1549
  if (!isAuthenticated) {
1549
1550
  setAccountProfileSummary(null);
@@ -1563,7 +1564,10 @@ export function StandaloneLayout(props) {
1563
1564
  setTeamManagementAccessLoading(canAccessCloudTeamManagement);
1564
1565
  setTeamManagementAccessError(null);
1565
1566
  try {
1566
- const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, { identityKey: authUserId });
1567
+ const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, {
1568
+ identityKey: authUserId,
1569
+ force: options?.force === true
1570
+ });
1567
1571
  const nextRole = payload?.workspaceRole === 'owner'
1568
1572
  || payload?.workspaceRole === 'admin'
1569
1573
  || payload?.workspaceRole === 'member'
@@ -1611,6 +1615,24 @@ export function StandaloneLayout(props) {
1611
1615
  markAccountSurfaceLoadSettled('teamManagement');
1612
1616
  }
1613
1617
  }, [authUserId, canAccessCloudTeamManagement, isAuthenticated, markAccountSurfaceLoadSettled, resolveCloudAuthUrl]);
1618
+ const applyPlansAccountProfileSummary = useCallback((payload) => {
1619
+ setAccountProfileSummary(payload);
1620
+ const nextRole = payload?.workspaceRole === 'owner'
1621
+ || payload?.workspaceRole === 'admin'
1622
+ || payload?.workspaceRole === 'member'
1623
+ || payload?.workspaceRole === 'read-only'
1624
+ ? payload.workspaceRole
1625
+ : null;
1626
+ const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
1627
+ ? payload.teamPlanMode
1628
+ : 'unknown';
1629
+ setTeamWorkspaceRole(nextRole);
1630
+ setTeamPlanMode(nextMode);
1631
+ }, []);
1632
+ const hasActiveCommercialEntitlement = useMemo(() => {
1633
+ const entitlementState = String(accountProfileSummary?.entitlementState || '').trim().toLowerCase();
1634
+ return ACTIVE_COMMERCIAL_STATES.has(entitlementState);
1635
+ }, [accountProfileSummary?.entitlementState]);
1614
1636
  const handleStartCheckout = useCallback(async () => {
1615
1637
  const params = new URLSearchParams();
1616
1638
  params.set('screen', 'plans');
@@ -1798,6 +1820,47 @@ export function StandaloneLayout(props) {
1798
1820
  }
1799
1821
  navigate(`/setup?${params.toString()}`, { replace: true });
1800
1822
  }, [navigate]);
1823
+ const handlePlansBack = useCallback(async () => {
1824
+ if (plansNavigationBusy)
1825
+ return;
1826
+ if (!isAuthenticated) {
1827
+ closePlansScreen();
1828
+ return;
1829
+ }
1830
+ const summary = await loadAccountProfileSummary({ force: true });
1831
+ const entitlementState = String(summary?.entitlementState || '').trim().toLowerCase();
1832
+ if (!ACTIVE_COMMERCIAL_STATES.has(entitlementState)) {
1833
+ return;
1834
+ }
1835
+ setPlansNavigationBusy(true);
1836
+ try {
1837
+ const result = await props.continueAfterCommercialOnboarding();
1838
+ if (!result.success) {
1839
+ pushNotice(result.error || 'Unable to finish onboarding.', 'error');
1840
+ if (result.destination === 'login') {
1841
+ navigate('/login', { replace: true });
1842
+ }
1843
+ return;
1844
+ }
1845
+ if (result.destination === 'setup') {
1846
+ openWorkspaceSetup();
1847
+ return;
1848
+ }
1849
+ closePlansScreen();
1850
+ }
1851
+ finally {
1852
+ setPlansNavigationBusy(false);
1853
+ }
1854
+ }, [
1855
+ closePlansScreen,
1856
+ isAuthenticated,
1857
+ loadAccountProfileSummary,
1858
+ navigate,
1859
+ openWorkspaceSetup,
1860
+ plansNavigationBusy,
1861
+ props,
1862
+ pushNotice
1863
+ ]);
1801
1864
  const handleSwitchWorkspace = useCallback(async (workspaceId) => {
1802
1865
  if (!workspaceId || workspaceActionBusy)
1803
1866
  return;
@@ -2545,6 +2608,9 @@ export function StandaloneLayout(props) {
2545
2608
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
2546
2609
  setShowAccountMenu(false);
2547
2610
  await logout();
2611
+ if (runtimeMode === 'cloud') {
2612
+ navigate('/login', { replace: true });
2613
+ }
2548
2614
  }, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
2549
2615
  const cycleEmptyColumnVisibility = useCallback(() => {
2550
2616
  setEmptyColumnMode((prev) => {
@@ -3180,7 +3246,7 @@ export function StandaloneLayout(props) {
3180
3246
  display: 'inline-flex',
3181
3247
  alignItems: 'center',
3182
3248
  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: () => {
3249
+ }, 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
3250
  setRequestedDocPath(null);
3185
3251
  setRequestedDocAssetId(null);
3186
3252
  }, 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 +3306,16 @@ export function StandaloneLayout(props) {
3240
3306
  top: '10px',
3241
3307
  right: '10px',
3242
3308
  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
3309
+ }, 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) => {
3310
+ if (currentTask?.isDeleted) {
3311
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
3312
+ if (!deletedRecord)
3313
+ return;
3314
+ void handleRestoreDeletedTask(deletedRecord.id);
3315
+ return;
3316
+ }
3317
+ void handleUnarchive(taskId);
3318
+ }, handleRestoreDeletedTask: currentTask?.deletedRecordId
3244
3319
  ? (deletedRecordId) => {
3245
3320
  void handleRestoreDeletedTask(deletedRecordId);
3246
3321
  }
@@ -3253,7 +3328,7 @@ export function StandaloneLayout(props) {
3253
3328
  resetForm();
3254
3329
  setActiveTab('tasks');
3255
3330
  }
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) => {
3331
+ : 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
3332
  setSettingsSection(section);
3258
3333
  setActiveTab('settings');
3259
3334
  }, 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');
@@ -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
@@ -1,5 +1,6 @@
1
1
  import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
2
- import type { DeletedTaskDetails, DeletedTaskRecord, DeletedTaskSource, TaskActor, TaskItem } from './Taskforce.js';
2
+ import type { TaskActor } from '../shared/taskActor.js';
3
+ import type { DeletedTaskDetails, DeletedTaskRecord, DeletedTaskSource, TaskItem } from './Taskforce.js';
3
4
  export interface DeletedTaskLifecycleServiceDeps {
4
5
  db: DatabaseAdapter;
5
6
  tenantId: string;
@@ -5,7 +5,7 @@ export interface PlanEntitlementServiceDeps {
5
5
  readGlobalConfig: () => GlobalTaskforceSettings;
6
6
  writeGlobalConfig: (config: GlobalTaskforceSettings) => void;
7
7
  }
8
- type CommercialStateTransitionSource = 'registration' | 'checkout-start' | 'checkout-confirm' | 'stripe-webhook' | 'backfill' | 'legacy-plan-mode' | 'admin' | 'system';
8
+ type CommercialStateTransitionSource = 'registration' | 'invite-accept' | 'checkout-start' | 'checkout-confirm' | 'stripe-webhook' | 'backfill' | 'legacy-plan-mode' | 'admin' | 'system';
9
9
  /**
10
10
  * Handles all plan catalog, plan version, and account entitlement logic.
11
11
  * Extracted from Taskforce.ts to reduce monolith size.
@@ -80,6 +80,7 @@ export declare class PlanEntitlementService {
80
80
  private getActiveDefaultPlanVersions;
81
81
  private getDefaultPlanVersionByMode;
82
82
  private tryGetDefaultPlanVersionByMode;
83
+ private tryGetImplicitFreeOnboardingPlanVersion;
83
84
  getOnboardingPolicy(): OnboardingPolicy;
84
85
  validateOnboardingPolicy(input?: Partial<OnboardingPolicy>): OnboardingPolicyValidationResult;
85
86
  resolveRegistrationOnboardingOutcome(selectedPlanVersionIdRaw?: string): {
@@ -92,6 +93,7 @@ export declare class PlanEntitlementService {
92
93
  createUserBillingRecord(userId: string, options?: {
93
94
  entitlementState?: string;
94
95
  planId?: string | null;
96
+ effectiveUntil?: string | null;
95
97
  }): void;
96
98
  getUserEntitlement(userId: string): {
97
99
  entitlementState: string;
@@ -152,7 +152,8 @@ export class PlanEntitlementService {
152
152
  `).run('pending_plan_selection', 'checkout-expired', now, this.tenantId, accountId);
153
153
  this.createUserBillingRecord(accountId, {
154
154
  entitlementState: 'pending_plan_selection',
155
- planId: null
155
+ planId: null,
156
+ effectiveUntil: null
156
157
  });
157
158
  return this.readAccountEntitlementRow(accountId);
158
159
  }
@@ -442,7 +443,8 @@ export class PlanEntitlementService {
442
443
  entitlementState: nextState,
443
444
  planId: input.billingMirrorPlanId !== undefined
444
445
  ? input.billingMirrorPlanId
445
- : (nextState === 'pending_plan_selection' ? null : planVersion.planId)
446
+ : (nextState === 'pending_plan_selection' ? null : planVersion.planId),
447
+ effectiveUntil: input.effectiveUntil || null
446
448
  });
447
449
  return this.getAccountEntitlement(accountId);
448
450
  }
@@ -627,18 +629,58 @@ export class PlanEntitlementService {
627
629
  throw error;
628
630
  }
629
631
  }
632
+ tryGetImplicitFreeOnboardingPlanVersion() {
633
+ const rows = this.db.prepare(`
634
+ SELECT DISTINCT v.plan_version_id
635
+ FROM plan_versions v
636
+ JOIN plan_catalog p
637
+ ON p.tenant_id = v.tenant_id
638
+ AND p.plan_id = v.plan_id
639
+ JOIN billing_price_mappings bpm
640
+ ON bpm.tenant_id = v.tenant_id
641
+ AND bpm.plan_version_id = v.plan_version_id
642
+ WHERE v.tenant_id = ?
643
+ AND COALESCE(p.status, 'active') = 'active'
644
+ AND bpm.active = 1
645
+ AND bpm.pricing_type = 'free'
646
+ ORDER BY v.is_default DESC, p.updated_at DESC, p.created_at DESC, v.version_number DESC, v.created_at DESC
647
+ `).all(this.tenantId);
648
+ for (const row of rows) {
649
+ const planVersionId = String(row.plan_version_id || '').trim();
650
+ if (!planVersionId)
651
+ continue;
652
+ const version = this.getPlanVersionSnapshot(planVersionId);
653
+ if (!this.isTeamPlanVersion(version)) {
654
+ return version;
655
+ }
656
+ }
657
+ return null;
658
+ }
630
659
  getOnboardingPolicy() {
631
660
  const billing = this.deps.readGlobalConfig().billing || {};
632
661
  const policy = billing.onboardingPolicy && typeof billing.onboardingPolicy === 'object'
633
662
  ? billing.onboardingPolicy
634
663
  : null;
664
+ if (policy?.mode === 'require_plan_selection'
665
+ || policy?.mode === 'auto_grant_free'
666
+ || policy?.mode === 'auto_start_trial') {
667
+ return {
668
+ mode: policy.mode,
669
+ planVersionId: typeof policy.planVersionId === 'string'
670
+ ? (policy.planVersionId.trim() || null)
671
+ : null
672
+ };
673
+ }
674
+ const implicitFreeVersion = this.tryGetImplicitFreeOnboardingPlanVersion();
675
+ if (implicitFreeVersion) {
676
+ return {
677
+ mode: 'auto_grant_free',
678
+ planVersionId: implicitFreeVersion.planVersionId
679
+ };
680
+ }
635
681
  return {
636
- mode: policy?.mode === 'auto_grant_free' || policy?.mode === 'auto_start_trial'
637
- ? policy.mode
638
- : 'require_plan_selection',
639
- planVersionId: typeof policy?.planVersionId === 'string'
640
- ? (policy.planVersionId.trim() || null)
641
- : null
682
+ mode: 'require_plan_selection',
683
+ planVersionId: null
642
684
  };
643
685
  }
644
686
  validateOnboardingPolicy(input) {
@@ -761,14 +803,22 @@ export class PlanEntitlementService {
761
803
  const planId = typeof options?.planId === 'string'
762
804
  ? options.planId.trim().toLowerCase()
763
805
  : '';
806
+ const shouldUpdateEffectiveUntil = options ? Object.prototype.hasOwnProperty.call(options, 'effectiveUntil') : false;
807
+ const effectiveUntil = shouldUpdateEffectiveUntil
808
+ ? (typeof options?.effectiveUntil === 'string' ? options.effectiveUntil : null)
809
+ : null;
764
810
  this.db.prepare(`
765
- INSERT INTO user_billing (user_id, tenant_id, entitlement_state, tier, created_at, updated_at)
766
- VALUES (?, ?, ?, ?, ?, ?)
811
+ INSERT INTO user_billing (user_id, tenant_id, entitlement_state, tier, effective_until, created_at, updated_at)
812
+ VALUES (?, ?, ?, ?, ?, ?, ?)
767
813
  ON CONFLICT (user_id) DO UPDATE SET
768
814
  entitlement_state = excluded.entitlement_state,
769
815
  tier = excluded.tier,
816
+ effective_until = CASE
817
+ WHEN ? THEN excluded.effective_until
818
+ ELSE user_billing.effective_until
819
+ END,
770
820
  updated_at = excluded.updated_at
771
- `).run(userId, this.tenantId, entitlementState, planId, now, now);
821
+ `).run(userId, this.tenantId, entitlementState, planId, effectiveUntil, now, now, shouldUpdateEffectiveUntil ? 1 : 0);
772
822
  }
773
823
  getUserEntitlement(userId) {
774
824
  const account = this.getAccountEntitlement(userId);
@@ -22,7 +22,12 @@ function openCore(projectRoot) {
22
22
  storagePath: path.join(projectRoot, '.taskforce'),
23
23
  tenantId: path.basename(projectRoot)
24
24
  });
25
- core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter' });
25
+ core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
26
+ core.updatePlanVersion({
27
+ planId: 'starter',
28
+ versionNumber: 1,
29
+ seatLimit: 1
30
+ });
26
31
  core.updatePlanVersionFeatures({
27
32
  planVersionId: 'starter-v1',
28
33
  features: [
@@ -33,9 +38,39 @@ function openCore(projectRoot) {
33
38
  return core;
34
39
  }
35
40
  describe('TaskforceCore onboarding policy', () => {
36
- it('defaults to require_plan_selection with no plan version', () => {
41
+ it('defaults to auto_grant_free when a free personal plan version is available', () => {
37
42
  const projectRoot = makeProjectRoot();
38
43
  const core = openCore(projectRoot);
44
+ try {
45
+ expect(core.getOnboardingPolicy()).toEqual({
46
+ mode: 'auto_grant_free',
47
+ planVersionId: 'starter-v1'
48
+ });
49
+ }
50
+ finally {
51
+ core.close();
52
+ fs.rmSync(projectRoot, { recursive: true, force: true });
53
+ }
54
+ });
55
+ it('falls back to require_plan_selection when no free onboarding plan is available', () => {
56
+ const projectRoot = makeProjectRoot();
57
+ const core = new TaskforceCore({
58
+ projectRoot,
59
+ storagePath: path.join(projectRoot, '.taskforce'),
60
+ tenantId: path.basename(projectRoot)
61
+ });
62
+ core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
63
+ core.updatePlanVersion({
64
+ planId: 'starter',
65
+ versionNumber: 1,
66
+ seatLimit: 1
67
+ });
68
+ core.updatePlanVersionFeatures({
69
+ planVersionId: 'starter-v1',
70
+ features: [
71
+ { featureKey: 'workspace.switching', access: 'enabled' }
72
+ ]
73
+ });
39
74
  try {
40
75
  expect(core.getOnboardingPolicy()).toEqual({
41
76
  mode: 'require_plan_selection',
@@ -1,5 +1,6 @@
1
1
  import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
2
- import type { Comment, TaskActivityItem, TaskActor, TaskEvent, TaskEventDetails } from './Taskforce.js';
2
+ import type { TaskActor } from '../shared/taskActor.js';
3
+ import type { Comment, TaskActivityItem, TaskEvent, TaskEventDetails } from './Taskforce.js';
3
4
  export interface TaskActivityServiceDeps {
4
5
  db: DatabaseAdapter;
5
6
  tenantId: string;
@@ -7,6 +8,7 @@ export interface TaskActivityServiceDeps {
7
8
  normalizeWorkspaceId: (workspaceId: string | undefined) => string;
8
9
  resolveWorkspaceActor: (workspaceId: string, actorRef: unknown) => TaskActor | null;
9
10
  }
11
+ export declare function listWorkspaceTaskEventsService(deps: TaskActivityServiceDeps, workspaceId: string): TaskEvent[];
10
12
  export declare function replaceTaskCommentsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, comments: Comment[]): void;
11
13
  export declare function listTaskEventsInternalService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string): TaskEvent[];
12
14
  export declare function buildTaskActivityService(comments: Comment[], taskEvents: TaskEvent[]): TaskActivityItem[];
@@ -19,3 +21,4 @@ export declare function addTaskEventService(deps: TaskActivityServiceDeps, input
19
21
  details?: TaskEventDetails;
20
22
  }): void;
21
23
  export declare function replaceTaskEventsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, taskEvents: TaskEvent[]): void;
24
+ export declare function upsertTaskEventForSyncService(deps: TaskActivityServiceDeps, event: TaskEvent): TaskEvent;
@@ -39,6 +39,17 @@ function mapTaskEventRow(deps, row) {
39
39
  actorProfile: actor === 'system' ? null : deps.resolveWorkspaceActor(workspaceId, actor)
40
40
  };
41
41
  }
42
+ export function listWorkspaceTaskEventsService(deps, workspaceId) {
43
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
44
+ const rows = deps.db.prepare(`
45
+ SELECT id, task_id, workspace_id, action, actor, actor_type, details_json, created_at
46
+ FROM task_events
47
+ WHERE tenant_id = ?
48
+ AND workspace_id = ?
49
+ ORDER BY created_at ASC, id ASC
50
+ `).all(deps.tenantId, normalizedWorkspaceId);
51
+ return rows.map((row) => mapTaskEventRow(deps, row));
52
+ }
42
53
  export function replaceTaskCommentsService(deps, taskId, workspaceId, comments) {
43
54
  const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
44
55
  deps.db.prepare('DELETE FROM comments WHERE tenant_id = ? AND workspace_id = ? AND task_id = ?')
@@ -128,3 +139,37 @@ export function replaceTaskEventsService(deps, taskId, workspaceId, taskEvents)
128
139
  insertTaskEvent.run(event.id, deps.tenantId, normalizedWorkspaceId, taskId, event.action, event.actor, event.actorType, JSON.stringify(normalizeTaskEventDetails(event.details)), event.createdAt);
129
140
  }
130
141
  }
142
+ export function upsertTaskEventForSyncService(deps, event) {
143
+ const workspaceId = deps.normalizeWorkspaceId(event.workspaceId);
144
+ const normalizedEvent = {
145
+ id: String(event.id || '').trim(),
146
+ taskId: String(event.taskId || '').trim(),
147
+ workspaceId,
148
+ action: String(event.action || '').trim(),
149
+ actor: String(event.actor || 'system').trim() || 'system',
150
+ actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
151
+ ? event.actorType
152
+ : 'system',
153
+ createdAt: String(event.createdAt || '').trim() || new Date().toISOString(),
154
+ details: normalizeTaskEventDetails(event.details)
155
+ };
156
+ deps.db.prepare(`
157
+ INSERT INTO task_events (
158
+ id, tenant_id, workspace_id, task_id, action, actor, actor_type, details_json, created_at
159
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
160
+ ON CONFLICT (id) DO UPDATE SET
161
+ workspace_id = excluded.workspace_id,
162
+ task_id = excluded.task_id,
163
+ action = excluded.action,
164
+ actor = excluded.actor,
165
+ actor_type = excluded.actor_type,
166
+ details_json = excluded.details_json,
167
+ created_at = excluded.created_at
168
+ `).run(normalizedEvent.id, deps.tenantId, workspaceId, normalizedEvent.taskId, normalizedEvent.action, normalizedEvent.actor, normalizedEvent.actorType, JSON.stringify(normalizedEvent.details), normalizedEvent.createdAt);
169
+ return {
170
+ ...normalizedEvent,
171
+ actorProfile: normalizedEvent.actor === 'system'
172
+ ? null
173
+ : deps.resolveWorkspaceActor(workspaceId, normalizedEvent.actor)
174
+ };
175
+ }