@taskforcehq/taskforce 0.3.306 → 0.3.308

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 (232) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.d.ts +20 -15
  38. package/dist/components/views/PlanComparisonPage.js +221 -82
  39. package/dist/components/views/PlanComparisonPage.test.js +268 -14
  40. package/dist/components/views/PlansPage.js +16 -10
  41. package/dist/components/views/PlansPage.test.js +4 -2
  42. package/dist/components/views/StandaloneLayout.js +256 -69
  43. package/dist/components/views/WidgetView.js +10 -8
  44. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  45. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  46. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  47. package/dist/config/envSchema.js +1 -0
  48. package/dist/core/AiProfileService.d.ts +7 -0
  49. package/dist/core/AiProfileService.js +460 -35
  50. package/dist/core/AiProfiles.test.js +648 -2
  51. package/dist/core/AttachmentLinkService.d.ts +3 -0
  52. package/dist/core/AttachmentLinkService.js +58 -13
  53. package/dist/core/GlobalSettingsService.js +30 -0
  54. package/dist/core/GlobalSettingsService.test.d.ts +1 -0
  55. package/dist/core/GlobalSettingsService.test.js +45 -0
  56. package/dist/core/McpTokenService.d.ts +12 -0
  57. package/dist/core/McpTokenService.js +44 -1
  58. package/dist/core/PlanEntitlementService.d.ts +20 -0
  59. package/dist/core/PlanEntitlementService.js +215 -19
  60. package/dist/core/PlanFeatureCatalog.test.js +55 -0
  61. package/dist/core/PlanningEntities.test.js +366 -4
  62. package/dist/core/TaskActivityService.d.ts +28 -1
  63. package/dist/core/TaskActivityService.js +227 -46
  64. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  65. package/dist/core/TaskActivitySync.test.js +90 -0
  66. package/dist/core/TaskAuditTimeline.test.js +96 -0
  67. package/dist/core/Taskforce.d.ts +204 -26
  68. package/dist/core/Taskforce.ids.test.js +58 -0
  69. package/dist/core/Taskforce.js +781 -71
  70. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  71. package/dist/core/types.d.ts +10 -0
  72. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  73. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  74. package/dist/hooks/bootstrapTimeouts.js +8 -0
  75. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  76. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  77. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  78. package/dist/hooks/sync/orchestratorShared.js +61 -1
  79. package/dist/hooks/sync/recovery.js +8 -0
  80. package/dist/hooks/sync/recovery.test.js +5 -5
  81. package/dist/hooks/sync/transfers.d.ts +2 -1
  82. package/dist/hooks/sync/transfers.js +36 -9
  83. package/dist/hooks/sync/transfers.test.js +534 -2
  84. package/dist/hooks/useFilterSort.js +13 -13
  85. package/dist/hooks/useFilterSort.test.js +87 -1
  86. package/dist/hooks/useSyncOrchestrator.js +50 -5
  87. package/dist/hooks/useTaskforce.d.ts +85 -50
  88. package/dist/hooks/useTaskforce.js +44 -14
  89. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  90. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  91. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  92. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  93. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  95. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  96. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  97. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  98. package/dist/mcp/clientIntegrations.d.ts +1 -0
  99. package/dist/mcp/clientIntegrations.js +25 -0
  100. package/dist/mcp/clientIntegrations.test.js +14 -3
  101. package/dist/mcp/clientProfileDefaults.js +3 -3
  102. package/dist/mcp/collaborationRegistrar.js +7 -7
  103. package/dist/mcp/documentAssetRegistrar.js +113 -7
  104. package/dist/mcp/runtime.d.ts +1 -0
  105. package/dist/mcp/runtime.js +969 -122
  106. package/dist/mcp/runtime.test.js +1083 -11
  107. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  108. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  109. package/dist/mcp/toolRegistry.d.ts +56 -0
  110. package/dist/mcp/toolRegistry.js +8 -0
  111. package/dist/mcp/toolRegistry.test.js +4 -0
  112. package/dist/migrations/taskSchemaMigrations.js +58 -15
  113. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  114. package/dist/server/aiProfileAvatarAssets.js +319 -0
  115. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  116. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  117. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  118. package/dist/server/index.js +4 -0
  119. package/dist/server/index.test.js +6 -1
  120. package/dist/server/routes/admin.js +328 -9
  121. package/dist/server/routes/annotatedAttachments.js +51 -0
  122. package/dist/server/routes/auth.d.ts +11 -0
  123. package/dist/server/routes/auth.js +189 -27
  124. package/dist/server/routes/authSupport.d.ts +5 -0
  125. package/dist/server/routes/authSupport.js +13 -1
  126. package/dist/server/routes/billing.d.ts +1 -0
  127. package/dist/server/routes/billing.js +116 -63
  128. package/dist/server/routes/billing.test.js +162 -11
  129. package/dist/server/routes/documents.js +10 -0
  130. package/dist/server/routes/shared.js +0 -10
  131. package/dist/server/routes/sync.integration.test.js +221 -0
  132. package/dist/server/routes/sync.js +13 -3
  133. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  134. package/dist/server/routes/tasks.js +12 -9
  135. package/dist/server/routes.js +87 -8
  136. package/dist/server/routes.test.js +972 -23
  137. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  138. package/dist/shared/aiProfileSurfaceType.js +10 -8
  139. package/dist/storage/objectStorage.d.ts +1 -0
  140. package/dist/storage/objectStorage.js +2 -1
  141. package/dist/storage/objectStorageClient.d.ts +1 -0
  142. package/dist/storage/objectStorageClient.js +41 -8
  143. package/dist/storage/objectStorageClient.test.js +26 -1
  144. package/dist/storage/objectStorageConfig.js +3 -1
  145. package/dist/storage/objectStorageConfig.test.js +13 -1
  146. package/dist/sync/collaborationSyncPayload.js +2 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -3
  149. package/dist/sync/collaborationSyncState.js +19 -12
  150. package/dist/sync/contentSyncPayload.d.ts +5 -1
  151. package/dist/sync/contentSyncPayload.js +9 -0
  152. package/dist/sync/contentSyncPayload.test.js +39 -0
  153. package/dist/sync/contentSyncState.d.ts +5 -1
  154. package/dist/sync/contentSyncState.js +231 -37
  155. package/dist/sync/planningSyncPayload.js +5 -0
  156. package/dist/sync/planningSyncPayload.test.js +24 -0
  157. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  158. package/dist/sync/syncApplyHandlers.js +89 -31
  159. package/dist/sync/syncApplyHandlers.test.js +184 -4
  160. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  161. package/dist/sync/syncResourceAdapters.js +2 -0
  162. package/dist/sync/syncService.d.ts +1 -0
  163. package/dist/sync/syncService.js +13 -1
  164. package/dist/sync/syncService.test.js +39 -0
  165. package/dist/sync/workspacePullFeed.d.ts +4 -0
  166. package/dist/sync/workspacePullFeed.js +129 -70
  167. package/dist/sync/workspacePullFeed.test.js +49 -0
  168. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  169. package/dist/sync/workspaceSyncModel.js +159 -45
  170. package/dist/sync/workspaceSyncModel.test.js +269 -3
  171. package/dist/types.d.ts +33 -2
  172. package/dist/ui/agent-logos/ChatGPT.png +0 -0
  173. package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
  174. package/dist/ui/agent-logos/antigravity.jpeg +0 -0
  175. package/dist/ui/agent-logos/claude-code.jpeg +0 -0
  176. package/dist/ui/agent-logos/codex.jpeg +0 -0
  177. package/dist/ui/agent-logos/cursor.png +0 -0
  178. package/dist/ui/agent-logos/openclaw.jpeg +0 -0
  179. package/dist/ui/agent-logos/windsurf.png +0 -0
  180. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
  181. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  182. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
  183. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
  184. package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
  185. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  186. package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
  187. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  188. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
  189. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
  190. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
  191. package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
  192. package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
  193. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
  194. package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
  195. package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
  196. package/dist/ui/assets/index-MAHKvFqp.js +4 -0
  197. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  198. package/dist/ui/index.html +3 -3
  199. package/dist/ui/og-image.png +0 -0
  200. package/dist/ui/product/agent manager.png +0 -0
  201. package/dist/ui/product/agent_manager_02.png +0 -0
  202. package/dist/utils/aiProfileEvents.d.ts +7 -0
  203. package/dist/utils/aiProfileEvents.js +6 -0
  204. package/dist/utils/assignees.d.ts +6 -0
  205. package/dist/utils/assignees.js +8 -1
  206. package/dist/utils/assignees.test.d.ts +1 -0
  207. package/dist/utils/assignees.test.js +25 -0
  208. package/dist/utils/avatarDisplay.d.ts +1 -0
  209. package/dist/utils/avatarDisplay.js +15 -0
  210. package/dist/utils/contextFiles.d.ts +3 -1
  211. package/dist/utils/contextFiles.js +2 -1
  212. package/dist/utils/taskActivity.d.ts +7 -1
  213. package/dist/utils/taskActivity.js +63 -1
  214. package/dist/utils/taskActivity.test.js +23 -1
  215. package/dist/utils/taskSearch.d.ts +22 -0
  216. package/dist/utils/taskSearch.js +87 -0
  217. package/dist/utils/taskSearch.test.d.ts +1 -0
  218. package/dist/utils/taskSearch.test.js +69 -0
  219. package/dist/utils/taskUtils.js +10 -3
  220. package/dist/utils/taskUtils.test.d.ts +1 -0
  221. package/dist/utils/taskUtils.test.js +19 -0
  222. package/package.json +1 -1
  223. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  224. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  225. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  226. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  227. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  228. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  229. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  230. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  231. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  232. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from 'react';
3
3
  import { CheckCircle2, Loader2 } from 'lucide-react';
4
4
  import taskforceStyles from '../../Taskforce.module.css';
@@ -19,6 +19,60 @@ function formatPricingSource(pricingSource) {
19
19
  return `${environment} (${runtimeMode})`;
20
20
  return environment || runtimeMode || null;
21
21
  }
22
+ const ALLOWED_PRICING_DESCRIPTION_TAGS = new Set(['a', 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'u', 'ul']);
23
+ function escapeHtmlText(raw) {
24
+ return raw
25
+ .replace(/&/g, '&')
26
+ .replace(/</g, '&lt;')
27
+ .replace(/>/g, '&gt;')
28
+ .replace(/"/g, '&quot;')
29
+ .replace(/'/g, '&#39;');
30
+ }
31
+ function sanitizePricingDescriptionHtml(raw) {
32
+ const value = String(raw || '').trim();
33
+ if (!value)
34
+ return '';
35
+ if (typeof document === 'undefined')
36
+ return escapeHtmlText(value);
37
+ const template = document.createElement('template');
38
+ template.innerHTML = value;
39
+ const output = document.createElement('div');
40
+ const cleanNode = (node) => {
41
+ if (node.nodeType === Node.TEXT_NODE) {
42
+ return document.createTextNode(node.textContent || '');
43
+ }
44
+ if (node.nodeType !== Node.ELEMENT_NODE)
45
+ return null;
46
+ const element = node;
47
+ const tagName = element.tagName.toLowerCase();
48
+ if (tagName === 'script' || tagName === 'style')
49
+ return null;
50
+ const children = Array.from(element.childNodes).map(cleanNode).filter((child) => Boolean(child));
51
+ if (!ALLOWED_PRICING_DESCRIPTION_TAGS.has(tagName)) {
52
+ const fragment = document.createDocumentFragment();
53
+ for (const child of children)
54
+ fragment.appendChild(child);
55
+ return fragment;
56
+ }
57
+ const next = document.createElement(tagName);
58
+ if (tagName === 'a') {
59
+ const href = String(element.getAttribute('href') || '').trim();
60
+ if (/^(https?:|mailto:|\/|#)/i.test(href)) {
61
+ next.setAttribute('href', href);
62
+ next.setAttribute('rel', 'noopener noreferrer');
63
+ }
64
+ }
65
+ for (const child of children)
66
+ next.appendChild(child);
67
+ return next;
68
+ };
69
+ for (const child of Array.from(template.content.childNodes)) {
70
+ const cleaned = cleanNode(child);
71
+ if (cleaned)
72
+ output.appendChild(cleaned);
73
+ }
74
+ return output.innerHTML;
75
+ }
22
76
  function formatCurrency(amount, currency) {
23
77
  if (amount == null || !currency)
24
78
  return 'Contact Sales';
@@ -34,7 +88,7 @@ function formatCurrency(amount, currency) {
34
88
  }
35
89
  }
36
90
  function renderFreePriceAmount() {
37
- return '$0.00';
91
+ return '$0';
38
92
  }
39
93
  function isSelectablePrice(price) {
40
94
  if (!price)
@@ -50,6 +104,25 @@ function renderPriceLabel(price) {
50
104
  return renderFreePriceAmount();
51
105
  return formatCurrency(price.unitAmount, price.currency);
52
106
  }
107
+ function getSelectedPricing(regularPrice, campaignPrice) {
108
+ return isSelectablePrice(campaignPrice)
109
+ ? { price: campaignPrice, pricingAudience: 'campaign' }
110
+ : { price: regularPrice, pricingAudience: 'public' };
111
+ }
112
+ function renderPriceRows(monthPrice, yearPrice, campaignMonthPrice, campaignYearPrice) {
113
+ const hasMonth = isSelectablePrice(monthPrice) || isSelectablePrice(campaignMonthPrice);
114
+ const hasYear = isSelectablePrice(yearPrice) || isSelectablePrice(campaignYearPrice);
115
+ if (!hasMonth && !hasYear)
116
+ return null;
117
+ const renderRow = (price, campaignPrice, interval) => {
118
+ if (!isSelectablePrice(price) && !isSelectablePrice(campaignPrice))
119
+ return null;
120
+ const hasCampaignPrice = isSelectablePrice(campaignPrice);
121
+ const intervalLabel = interval === 'year' ? 'year' : 'month';
122
+ return (_jsxs("div", { className: `${styles.priceRow} ${hasCampaignPrice ? styles.priceRowWithCampaign : ''}`, children: [isSelectablePrice(price) ? (_jsxs("div", { className: styles.regularPriceLine, children: [_jsx("span", { className: `${styles.priceRowAmount} ${hasCampaignPrice ? styles.regularPriceDiscounted : ''}`, children: renderPriceLabel(price) }), _jsxs("span", { className: styles.priceRowInterval, children: ["/ ", intervalLabel] })] })) : null, hasCampaignPrice ? (_jsxs("div", { className: styles.campaignPriceLine, children: [_jsx("span", { className: styles.campaignPriceLabel, children: "Founding Offer:" }), _jsx("span", { className: styles.campaignPriceAmount, children: renderPriceLabel(campaignPrice) }), _jsxs("span", { className: styles.campaignPriceInterval, children: ["/ ", intervalLabel] })] })) : null] }, interval));
123
+ };
124
+ return (_jsxs("div", { className: styles.priceRows, children: [renderRow(monthPrice, campaignMonthPrice, 'month'), renderRow(yearPrice, campaignYearPrice, 'year')] }));
125
+ }
53
126
  function getPlanSortRank(plan) {
54
127
  const prices = [plan.pricing.month, plan.pricing.year].filter(Boolean);
55
128
  if (prices.some((price) => price.pricingType === 'free'))
@@ -74,38 +147,72 @@ function readConfiguredFeatureLimit(config, key) {
74
147
  const normalized = Math.max(1, Math.floor(numericValue));
75
148
  return normalized;
76
149
  }
150
+ function formatLimitedFeatureLabel(baseLabel, limitValue) {
151
+ if (limitValue !== 1)
152
+ return baseLabel;
153
+ return baseLabel
154
+ .replace(/\b([A-Za-z]+)ies\b$/, '$1y')
155
+ .replace(/\b([A-Za-z]+[^s\s])s\b$/, '$1');
156
+ }
157
+ function formatStorageLimit(storageLimitMb) {
158
+ if (storageLimitMb <= 1000)
159
+ return { value: String(storageLimitMb), unit: 'MB' };
160
+ return {
161
+ value: String(Math.round(storageLimitMb / 1000)),
162
+ unit: 'GB'
163
+ };
164
+ }
77
165
  function getFeatureDisplayParts(feature, seatLimit) {
78
166
  const baseLabel = String(feature.publicLabel || feature.label || '').trim() || formatFeatureKeyFallback(feature.featureKey);
79
167
  const config = feature.config && typeof feature.config === 'object' ? feature.config : {};
80
168
  if (feature.featureKey === 'context.uploads') {
81
169
  const storageLimitMb = readConfiguredFeatureLimit(config, 'storageLimitMb');
82
170
  if (storageLimitMb !== null) {
83
- return { limitValue: String(storageLimitMb), limitUnit: 'MB', baseLabel };
171
+ const storageLimit = formatStorageLimit(storageLimitMb);
172
+ return {
173
+ limitValue: storageLimit.value,
174
+ limitUnit: storageLimit.unit,
175
+ baseLabel: formatLimitedFeatureLabel(baseLabel, storageLimitMb)
176
+ };
84
177
  }
85
178
  }
86
179
  if (feature.featureKey === 'workspace.workspaces') {
87
180
  const maxWorkspaces = readConfiguredFeatureLimit(config, 'maxWorkspaces');
88
181
  if (maxWorkspaces !== null) {
89
- return { limitValue: String(maxWorkspaces), limitUnit: 'x', baseLabel };
182
+ return {
183
+ limitValue: String(maxWorkspaces),
184
+ limitUnit: null,
185
+ baseLabel: formatLimitedFeatureLabel(baseLabel, maxWorkspaces)
186
+ };
90
187
  }
91
188
  }
92
189
  if (feature.featureKey === 'collaboration.ai_profiles') {
93
190
  const maxAiProfiles = readConfiguredFeatureLimit(config, 'maxAiProfiles');
94
191
  if (maxAiProfiles !== null) {
95
- return { limitValue: String(maxAiProfiles), limitUnit: 'x', baseLabel };
192
+ return {
193
+ limitValue: String(maxAiProfiles),
194
+ limitUnit: null,
195
+ baseLabel: formatLimitedFeatureLabel(baseLabel, maxAiProfiles)
196
+ };
96
197
  }
97
198
  }
98
199
  if (feature.featureKey === 'collaboration.team_management') {
99
200
  const normalizedSeatLimit = Number(seatLimit);
100
201
  if (Number.isFinite(normalizedSeatLimit) && normalizedSeatLimit > 0) {
101
- return { limitValue: String(Math.floor(normalizedSeatLimit)), limitUnit: 'x', baseLabel };
202
+ const seatLimitValue = Math.floor(normalizedSeatLimit);
203
+ return {
204
+ limitValue: String(seatLimitValue),
205
+ limitUnit: null,
206
+ baseLabel: formatLimitedFeatureLabel(baseLabel, seatLimitValue)
207
+ };
102
208
  }
103
209
  }
104
210
  return { limitValue: null, limitUnit: null, baseLabel };
105
211
  }
106
- export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome, footer, isAuthenticated, authSessionResolved = true, currentPlanId, currentPlanVersionId, currentEntitlementState, markCurrentPlan, canManageCurrentPlan = true, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled = false, topActions, shellOwnsScroll = false, showPageBackButton = true, showHeaderBarWithChrome = false, currentPlanCardActionMode = 'manage', fallbackPricingSource = null, onSelectPlan }) {
212
+ export function PlanComparisonPage({ heading, subtitle, variant = 'app', theme = 'dark', chrome, footer, isAuthenticated, authSessionResolved = true, currentPlanId, currentPlanVersionId, currentEntitlementState, markCurrentPlan, canManageCurrentPlan = true, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled = false, topActions, shellOwnsScroll = false, showPageBackButton = true, showHeaderBarWithChrome = false, preferPricingPageTitle = true, currentPlanCardActionMode = 'manage', fallbackPricingSource = null, onSelectPlan }) {
107
213
  const [plans, setPlans] = useState([]);
108
214
  const [pricingSource, setPricingSource] = useState(null);
215
+ const [pricingPageCopy, setPricingPageCopy] = useState({ pageTitle: null, pageDescription: null });
109
216
  const [loading, setLoading] = useState(true);
110
217
  const [error, setError] = useState(null);
111
218
  const [retryNonce, setRetryNonce] = useState(0);
@@ -124,6 +231,10 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
124
231
  if (active) {
125
232
  setPlans(Array.isArray(body?.plans) ? body.plans : []);
126
233
  setPricingSource(body?.pricingSource && typeof body.pricingSource === 'object' ? body.pricingSource : null);
234
+ setPricingPageCopy({
235
+ pageTitle: typeof body?.pageTitle === 'string' ? (body.pageTitle.trim() || null) : null,
236
+ pageDescription: typeof body?.pageDescription === 'string' ? (body.pageDescription.trim() || null) : null
237
+ });
127
238
  }
128
239
  return;
129
240
  }
@@ -134,6 +245,7 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
134
245
  const message = err instanceof Error ? err.message : 'Failed to load pricing';
135
246
  setError(message || 'Failed to load pricing');
136
247
  setPricingSource(null);
248
+ setPricingPageCopy({ pageTitle: null, pageDescription: null });
137
249
  }
138
250
  }
139
251
  finally {
@@ -156,13 +268,16 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
156
268
  return String(a.displayName || a.planId).localeCompare(String(b.displayName || b.planId));
157
269
  }), [plans]);
158
270
  const pricingSourceLabel = useMemo(() => formatPricingSource(pricingSource || fallbackPricingSource), [fallbackPricingSource, pricingSource]);
271
+ const resolvedHeading = String((preferPricingPageTitle ? pricingPageCopy.pageTitle : null) || heading).trim() || heading;
272
+ const resolvedSubtitle = String(pricingPageCopy.pageDescription || subtitle || '').trim();
273
+ const resolvedSubtitleHtml = useMemo(() => sanitizePricingDescriptionHtml(resolvedSubtitle), [resolvedSubtitle]);
159
274
  const planSelectionEnabled = authSessionResolved;
160
275
  const contentClassName = `${styles.plansWrapper} ${shellOwnsScroll ? styles.shellOwnsScroll : 'tf-scrollbar'} ${variant === 'site' ? styles.siteVariant : ''} ${chrome ? styles.withChrome : ''}`.trim();
161
276
  const wrapPage = (content) => {
162
277
  if (shellOwnsScroll) {
163
278
  return (_jsxs(_Fragment, { children: [chrome, content, footer] }));
164
279
  }
165
- return (_jsxs("div", { className: taskforceStyles.standaloneWrapper, "data-theme": "dark", children: [chrome, content, footer] }));
280
+ return (_jsxs("div", { className: taskforceStyles.standaloneWrapper, "data-theme": theme, children: [chrome, content, footer] }));
166
281
  };
167
282
  if (loading) {
168
283
  return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsx("div", { className: styles.contentFrame, children: _jsxs("div", { className: styles.loader, children: [_jsx(Loader2, { size: 48, className: styles.spinner }), _jsx("p", { children: "Loading plans..." })] }) }) }) }));
@@ -170,82 +285,106 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
170
285
  if (error) {
171
286
  return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsx("div", { className: styles.contentFrame, children: _jsxs("div", { className: styles.emptyState, children: [_jsx("h2", { children: "Connecting to plan pricing..." }), _jsx("p", { children: error }), _jsx("button", { className: styles.backBtn, onClick: () => setRetryNonce((value) => value + 1), children: "Retry" })] }) }) }) }));
172
287
  }
173
- return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsxs("div", { className: styles.contentFrame, children: [_jsxs("div", { className: styles.header, children: [(!chrome || showHeaderBarWithChrome) && (showPageBackButton || topActions) && (_jsxs("div", { className: styles.headerBar, children: [_jsx("div", { children: showPageBackButton ? (_jsx("button", { className: styles.backBtn, onClick: onBack, disabled: backDisabled, children: backLabel })) : null }), _jsx("div", { className: styles.headerActions, children: topActions })] })), _jsx("h1", { className: styles.title, children: heading }), subtitle && _jsx("p", { className: styles.subtitle, children: subtitle }), isDebugModeEnabled() && pricingSourceLabel ? (_jsxs("p", { className: styles.debugMeta, children: ["Pricing source: ", pricingSourceLabel] })) : null, statusBanner && (_jsx("div", { className: `${styles.statusBanner} ${statusBanner.type === 'success'
288
+ return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsxs("div", { className: styles.contentFrame, children: [_jsxs("div", { className: styles.header, children: [(!chrome || showHeaderBarWithChrome) && (showPageBackButton || topActions) && (_jsxs("div", { className: styles.headerBar, children: [_jsx("div", { children: showPageBackButton ? (_jsx("button", { className: styles.backBtn, onClick: onBack, disabled: backDisabled, children: backLabel })) : null }), _jsx("div", { className: styles.headerActions, children: topActions })] })), _jsx("h1", { className: styles.title, children: resolvedHeading }), resolvedSubtitleHtml && (_jsx("div", { className: styles.subtitle, dangerouslySetInnerHTML: { __html: resolvedSubtitleHtml } })), isDebugModeEnabled() && pricingSourceLabel ? (_jsxs("p", { className: styles.debugMeta, children: ["Pricing source: ", pricingSourceLabel] })) : null, statusBanner && (_jsx("div", { className: `${styles.statusBanner} ${statusBanner.type === 'success'
174
289
  ? styles.successBanner
175
- : (statusBanner.type === 'info' ? styles.infoBanner : styles.errorBanner)}`, children: statusBanner.message }))] }), sortedPlans.length === 0 ? (_jsxs("div", { className: `${styles.emptyState} ${styles.marketingEmptyState}`, children: [_jsx("span", { className: styles.emptyBadge, children: "Coming Soon" }), _jsx("h2", { children: "Paid plans are getting their final polish." }), _jsx("p", { children: "Taskforce pricing is on the way with flexible options for solo operators, teams, and larger rollouts. Check back soon for launch tiers, feature bundles, and early access details." }), _jsxs("div", { className: styles.emptyHighlights, "aria-label": "Upcoming pricing highlights", children: [_jsx("span", { children: "Launch-ready tiers" }), _jsx("span", { children: "Team billing controls" }), _jsx("span", { children: "Feature-based packaging" })] })] })) : (_jsx("div", { className: styles.grid, children: sortedPlans.map((plan) => {
176
- const monthPrice = plan.pricing.month;
177
- const yearPrice = plan.pricing.year;
178
- const hasMonth = isSelectablePrice(monthPrice);
179
- const hasYear = isSelectablePrice(yearPrice);
180
- const planVersionId = String(plan.defaultVersion?.planVersionId || '').trim();
181
- const hasActiveEntitlement = isCommercialEntitlementActive(currentEntitlementState);
182
- const shouldMarkCurrentPlan = markCurrentPlan ?? hasActiveEntitlement;
183
- const hasKnownLinkedPlan = Boolean(planVersionId
184
- && currentPlanVersionId
185
- && currentPlanVersionId === planVersionId) || Boolean(!currentPlanVersionId
186
- && Boolean(currentPlanId && currentPlanId === plan.planId));
187
- const isCurrent = shouldMarkCurrentPlan && hasKnownLinkedPlan && currentEntitlementState !== 'canceled';
188
- const defaultInterval = hasMonth ? 'month' : (hasYear ? 'year' : null);
189
- const defaultPrice = defaultInterval === 'year' ? yearPrice : monthPrice;
190
- const currentPlanShowsManageAction = currentPlanCardActionMode === 'manage';
191
- const currentPlanShowsIndicator = currentPlanCardActionMode === 'indicator';
192
- const canSelectCard = planSelectionEnabled
193
- && Boolean(planVersionId)
194
- && (isCurrent
195
- ? (currentPlanShowsManageAction && canManageCurrentPlan)
196
- : Boolean(defaultInterval));
197
- const handleCardSelect = () => {
198
- if (!planVersionId)
199
- return;
200
- if (isCurrent) {
201
- if (!currentPlanShowsManageAction)
202
- return;
203
- if (!canManageCurrentPlan)
204
- return;
205
- void onSelectPlan({
206
- planId: plan.planId,
207
- planVersionId,
208
- interval: defaultInterval || 'month',
209
- pricingType: defaultPrice?.pricingType || 'stripe',
210
- isCurrentPlan: true
211
- });
212
- return;
213
- }
214
- if (!defaultInterval)
215
- return;
216
- void onSelectPlan({
217
- planId: plan.planId,
218
- planVersionId,
219
- interval: defaultInterval,
220
- pricingType: defaultPrice?.pricingType || 'stripe',
221
- isCurrentPlan: false
222
- });
223
- };
224
- return (_jsxs("div", { className: `${styles.planCard} ${isCurrent ? styles.activePlanCard : ''} ${canSelectCard ? styles.clickablePlanCard : ''}`, "data-testid": `plan-card-${plan.planId}`, role: canSelectCard ? 'button' : undefined, tabIndex: canSelectCard ? 0 : undefined, onClick: canSelectCard ? handleCardSelect : undefined, onKeyDown: canSelectCard ? (event) => {
225
- if (event.key === 'Enter' || event.key === ' ') {
226
- event.preventDefault();
227
- handleCardSelect();
228
- }
229
- } : undefined, children: [isCurrent && _jsx("span", { className: styles.currentBadge, children: "Current Plan" }), _jsx("h3", { className: styles.planName, children: plan.displayName }), String(plan.description || '').trim() && (_jsx("p", { className: styles.planDescription, children: String(plan.description || '').trim() })), _jsx("div", { className: styles.featuresList, children: Array.isArray(plan.features) && plan.features.length > 0 ? plan.features.map((feature, idx) => (_jsxs("div", { className: styles.featureItem, children: [_jsx(CheckCircle2, { className: styles.featureIcon }), _jsxs("div", { className: styles.featureTextBlock, children: [(() => {
230
- const { limitValue, limitUnit, baseLabel } = getFeatureDisplayParts(feature, plan.defaultVersion?.seatLimit);
231
- return (_jsxs("span", { className: styles.featureText, children: [limitValue && (_jsxs("span", { className: styles.featureLimitAccent, children: [_jsx("span", { children: limitValue }), limitUnit ? (_jsx("span", { className: limitUnit === 'MB' ? styles.featureLimitUnit : undefined, children: limitUnit })) : null] })), _jsx("span", { children: limitValue ? ` ${baseLabel}` : baseLabel })] }));
232
- })(), String(feature.publicDescription || feature.description || '').trim() && (_jsx("span", { className: styles.featureDescription, children: String(feature.publicDescription || feature.description || '').trim() }))] })] }, `${feature.featureKey}-${idx}`))) : (_jsx("span", { className: styles.featureText, children: "No additional features included" })) }), (hasMonth || hasYear) ? (_jsxs("div", { className: styles.priceRows, children: [hasMonth ? (_jsxs("div", { className: styles.priceRow, children: [_jsx("span", { className: styles.priceRowAmount, children: renderPriceLabel(monthPrice) }), _jsx("span", { className: styles.priceRowInterval, children: "/ month" })] })) : null, hasYear ? (_jsxs("div", { className: styles.priceRow, children: [_jsx("span", { className: styles.priceRowAmount, children: renderPriceLabel(yearPrice) }), _jsx("span", { className: styles.priceRowInterval, children: "/ year" })] })) : null] })) : null, isCurrent ? (currentPlanShowsManageAction ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-manage-${plan.planId}`, onClick: (event) => {
233
- event.stopPropagation();
290
+ : (statusBanner.type === 'info' ? styles.infoBanner : styles.errorBanner)}`, children: statusBanner.message }))] }), _jsxs("section", { className: styles.foundingBeta, "aria-labelledby": "founding-beta-heading", children: [_jsx("h2", { id: "founding-beta-heading", children: "Founding beta" }), _jsx("p", { children: "Taskforce is in founding beta. You may run into rough edges while we improve cloud workspaces and agent workflows. Early members get access while the product is still forming, plus a direct role in shaping what comes next." })] }), sortedPlans.length === 0 ? (_jsxs("div", { className: `${styles.emptyState} ${styles.marketingEmptyState}`, children: [_jsx("span", { className: styles.emptyBadge, children: "Coming Soon" }), _jsx("h2", { children: "Paid plans are getting their final polish." }), _jsx("p", { children: "Taskforce pricing is on the way with flexible options for solo operators, teams, and larger rollouts. Check back soon for launch tiers, feature bundles, and early access details." }), _jsxs("div", { className: styles.emptyHighlights, "aria-label": "Upcoming pricing highlights", children: [_jsx("span", { children: "Launch-ready tiers" }), _jsx("span", { children: "Team billing controls" }), _jsx("span", { children: "Feature-based packaging" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grid, children: sortedPlans.map((plan) => {
291
+ const monthPrice = plan.pricing.month;
292
+ const yearPrice = plan.pricing.year;
293
+ const campaignMonthPrice = plan.campaignPricing?.month || null;
294
+ const campaignYearPrice = plan.campaignPricing?.year || null;
295
+ const selectedMonthPricing = getSelectedPricing(monthPrice, campaignMonthPrice);
296
+ const selectedYearPricing = getSelectedPricing(yearPrice, campaignYearPrice);
297
+ const hasMonth = isSelectablePrice(selectedMonthPricing.price);
298
+ const hasYear = isSelectablePrice(selectedYearPricing.price);
299
+ const hasMultipleBillingOptions = hasMonth && hasYear;
300
+ const planVersionId = String(plan.defaultVersion?.planVersionId || '').trim();
301
+ const hasActiveEntitlement = isCommercialEntitlementActive(currentEntitlementState);
302
+ const shouldMarkCurrentPlan = markCurrentPlan ?? hasActiveEntitlement;
303
+ const hasKnownLinkedPlan = Boolean(planVersionId
304
+ && currentPlanVersionId
305
+ && currentPlanVersionId === planVersionId) || Boolean(!currentPlanVersionId
306
+ && Boolean(currentPlanId && currentPlanId === plan.planId));
307
+ const isCurrent = shouldMarkCurrentPlan && hasKnownLinkedPlan && currentEntitlementState !== 'canceled';
308
+ const defaultInterval = hasMonth ? 'month' : (hasYear ? 'year' : null);
309
+ const defaultPricing = defaultInterval === 'year' ? selectedYearPricing : selectedMonthPricing;
310
+ const currentPlanShowsManageAction = currentPlanCardActionMode === 'manage';
311
+ const currentPlanShowsIndicator = currentPlanCardActionMode === 'indicator';
312
+ const canSelectCard = planSelectionEnabled
313
+ && Boolean(planVersionId)
314
+ && (isCurrent
315
+ ? (currentPlanShowsManageAction && canManageCurrentPlan)
316
+ : Boolean(defaultInterval));
317
+ const handleCardSelect = () => {
318
+ if (!planVersionId)
319
+ return;
320
+ if (isCurrent) {
321
+ if (!currentPlanShowsManageAction)
322
+ return;
234
323
  if (!canManageCurrentPlan)
235
324
  return;
236
- void onSelectPlan({ planId: plan.planId, planVersionId, interval: defaultInterval || 'month', pricingType: defaultPrice?.pricingType || 'stripe', isCurrentPlan: true });
237
- }, disabled: !planVersionId || !canManageCurrentPlan || !planSelectionEnabled, children: isAuthenticated
238
- ? (canManageCurrentPlan ? 'Manage Plan' : 'Current Plan')
239
- : 'Current Plan' })) : (currentPlanShowsIndicator ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-current-${plan.planId}`, disabled: true, children: "Current Plan" })) : null)) : (_jsxs("div", { className: styles.ctaRow, children: [hasMonth ? (_jsx("button", { className: `${styles.callToAction} ${styles.secondaryCta}`, "data-testid": `plan-select-${plan.planId}-month`, onClick: (event) => {
240
- event.stopPropagation();
241
- void onSelectPlan({ planId: plan.planId, planVersionId, interval: 'month', pricingType: monthPrice?.pricingType || 'stripe', isCurrentPlan: false });
242
- }, disabled: !planVersionId || !planSelectionEnabled, children: monthPrice?.pricingType === 'free'
243
- ? (isAuthenticated ? 'Select Free' : 'Get Started Free')
244
- : (isAuthenticated ? 'Choose Monthly' : 'Get Started Monthly') })) : null, hasYear ? (_jsx("button", { className: `${styles.callToAction} ${styles.primaryCta}`, "data-testid": `plan-select-${plan.planId}-year`, onClick: (event) => {
325
+ void onSelectPlan({
326
+ planId: plan.planId,
327
+ planVersionId,
328
+ interval: defaultInterval || 'month',
329
+ pricingType: defaultPricing.price?.pricingType || 'stripe',
330
+ pricingAudience: defaultPricing.pricingAudience,
331
+ isCurrentPlan: true
332
+ });
333
+ return;
334
+ }
335
+ if (!defaultInterval)
336
+ return;
337
+ void onSelectPlan({
338
+ planId: plan.planId,
339
+ planVersionId,
340
+ interval: defaultInterval,
341
+ pricingType: defaultPricing.price?.pricingType || 'stripe',
342
+ pricingAudience: defaultPricing.pricingAudience,
343
+ isCurrentPlan: false
344
+ });
345
+ };
346
+ return (_jsxs("div", { className: `${styles.planCard} ${isCurrent ? styles.activePlanCard : ''} ${canSelectCard ? styles.clickablePlanCard : ''}`, "data-testid": `plan-card-${plan.planId}`, role: canSelectCard ? 'button' : undefined, tabIndex: canSelectCard ? 0 : undefined, onClick: canSelectCard ? handleCardSelect : undefined, onKeyDown: canSelectCard ? (event) => {
347
+ if (event.key === 'Enter' || event.key === ' ') {
348
+ event.preventDefault();
349
+ handleCardSelect();
350
+ }
351
+ } : undefined, children: [isCurrent && _jsx("span", { className: styles.currentBadge, children: "Current Plan" }), _jsx("h3", { className: styles.planName, children: plan.displayName }), renderPriceRows(monthPrice, yearPrice, campaignMonthPrice, campaignYearPrice), String(plan.description || '').trim() && (_jsx("p", { className: styles.planDescription, children: String(plan.description || '').trim() })), _jsx("div", { className: styles.featuresList, children: Array.isArray(plan.features) && plan.features.length > 0 ? plan.features.map((feature, idx) => (_jsxs("div", { className: styles.featureItem, children: [_jsx(CheckCircle2, { className: styles.featureIcon }), _jsxs("div", { className: styles.featureTextBlock, children: [(() => {
352
+ const { limitValue, limitUnit, baseLabel } = getFeatureDisplayParts(feature, plan.defaultVersion?.seatLimit);
353
+ return (_jsxs("span", { className: styles.featureText, children: [limitValue && (_jsxs("span", { className: styles.featureLimitAccent, children: [_jsx("span", { children: limitValue }), limitUnit ? (_jsx("span", { className: styles.featureLimitUnit, children: limitUnit })) : null] })), _jsx("span", { children: limitValue ? ` ${baseLabel}` : baseLabel })] }));
354
+ })(), feature.publicDescriptionVisible !== false && String(feature.publicDescription || feature.description || '').trim() && (_jsx("span", { className: styles.featureDescription, children: String(feature.publicDescription || feature.description || '').trim() }))] })] }, `${feature.featureKey}-${idx}`))) : (_jsx("span", { className: styles.featureText, children: "No additional features included" })) }), isCurrent ? (currentPlanShowsManageAction ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-manage-${plan.planId}`, onClick: (event) => {
245
355
  event.stopPropagation();
246
- void onSelectPlan({ planId: plan.planId, planVersionId, interval: 'year', pricingType: yearPrice?.pricingType || 'stripe', isCurrentPlan: false });
247
- }, disabled: !planVersionId || !planSelectionEnabled, children: yearPrice?.pricingType === 'free'
248
- ? (isAuthenticated ? 'Select Free' : 'Get Started Free')
249
- : (isAuthenticated ? 'Choose Yearly' : 'Get Started Yearly') })) : null] }))] }, plan.planId));
250
- }) }))] }) }) }));
356
+ if (!canManageCurrentPlan)
357
+ return;
358
+ void onSelectPlan({
359
+ planId: plan.planId,
360
+ planVersionId,
361
+ interval: defaultInterval || 'month',
362
+ pricingType: defaultPricing.price?.pricingType || 'stripe',
363
+ pricingAudience: defaultPricing.pricingAudience,
364
+ isCurrentPlan: true
365
+ });
366
+ }, disabled: !planVersionId || !canManageCurrentPlan || !planSelectionEnabled, children: isAuthenticated
367
+ ? (canManageCurrentPlan ? 'Manage Plan' : 'Current Plan')
368
+ : 'Current Plan' })) : (currentPlanShowsIndicator ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-current-${plan.planId}`, disabled: true, children: "Current Plan" })) : null)) : (_jsxs("div", { className: styles.ctaRow, children: [hasMonth ? (_jsx("button", { className: `${styles.callToAction} ${styles.secondaryCta}`, "data-testid": `plan-select-${plan.planId}-month`, onClick: (event) => {
369
+ event.stopPropagation();
370
+ void onSelectPlan({
371
+ planId: plan.planId,
372
+ planVersionId,
373
+ interval: 'month',
374
+ pricingType: selectedMonthPricing.price?.pricingType || 'stripe',
375
+ pricingAudience: selectedMonthPricing.pricingAudience,
376
+ isCurrentPlan: false
377
+ });
378
+ }, disabled: !planVersionId || !planSelectionEnabled, children: hasMultipleBillingOptions ? 'Start Monthly' : 'Get Started' })) : null, hasYear ? (_jsx("button", { className: `${styles.callToAction} ${styles.primaryCta}`, "data-testid": `plan-select-${plan.planId}-year`, onClick: (event) => {
379
+ event.stopPropagation();
380
+ void onSelectPlan({
381
+ planId: plan.planId,
382
+ planVersionId,
383
+ interval: 'year',
384
+ pricingType: selectedYearPricing.price?.pricingType || 'stripe',
385
+ pricingAudience: selectedYearPricing.pricingAudience,
386
+ isCurrentPlan: false
387
+ });
388
+ }, disabled: !planVersionId || !planSelectionEnabled, children: hasMultipleBillingOptions ? 'Start Yearly' : 'Get Started' })) : null] }))] }, plan.planId));
389
+ }) }), _jsxs("div", { className: styles.pricingNotes, children: [_jsx("p", { children: "Prices are listed in USD. Taxes may apply." }), _jsx("p", { children: "Local install is free on every plan. Cloud limits apply only to hosted workspaces." })] }), _jsxs("section", { className: `${styles.foundingBeta} ${styles.cloudCapacityNote}`, "aria-labelledby": "cloud-capacity-heading", children: [_jsx("h2", { id: "cloud-capacity-heading", children: "Need more cloud capacity?" }), _jsx("p", { children: "Pro users can request expanded limits for cloud workspaces, AI agents, and storage." })] }), _jsxs("div", { className: `${styles.planCard} ${styles.planNoticeCard} ${styles.teamComingSoonCard}`, children: [_jsx("h2", { className: styles.planName, children: "Team workspaces are coming soon." }), _jsx("p", { className: styles.planDescription, children: "Shared workspaces, collaborators, team agent rosters, admin controls, and larger cloud limits are in development." })] })] }))] }) }) }));
251
390
  }