@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
@@ -32,11 +32,11 @@ describe('TaskContextUpload drag and drop', () => {
32
32
  const renderUpload = (contextFiles = [], taskId, extraProps) => render(_jsx(TaskContextUpload, { taskId: taskId, contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption, ...extraProps }));
33
33
  const openSection = async (user) => {
34
34
  const localUser = user ?? userEvent.setup();
35
- const showToggle = screen.queryByTitle('Show context documents');
36
- if (showToggle) {
35
+ if (!screen.queryByText('Drop files or URLs here')) {
36
+ const showToggle = await screen.findByTitle('Show context documents');
37
37
  await localUser.click(showToggle);
38
38
  }
39
- return screen.getByText('Drop files or URLs here');
39
+ return screen.findByText('Drop files or URLs here');
40
40
  };
41
41
  beforeEach(() => {
42
42
  vi.clearAllMocks();
@@ -14,12 +14,21 @@ import { Markdown } from "../ui/Markdown";
14
14
  import { IconMap, resolveColor } from "../../utils/constants";
15
15
  import { formatDate } from "../../utils/formatting";
16
16
  import { t } from "../../localization";
17
- import { TaskContextUpload } from "./TaskContextUpload";
18
- import { getTaskActivityItems, getTaskEventPrimaryChangeField, summarizeTaskChange, summarizeTaskEvent, } from "../../utils/taskActivity";
17
+ import { getTaskActivityItems } from "../../utils/taskActivity";
19
18
  import { ensureAssigneeOptionPresent, getAssigneeKind, getAssigneeLabel, } from "../../utils/assignees";
20
19
  import { getTaskReferenceLabel } from "../../utils/taskReferences";
21
20
  import { getVisibleTaskFormTaxonomies } from "../../utils/taxonomyFormDefaults.js";
22
21
  import { getInitiativeReferenceLabel, getWorkstreamReferenceLabel, } from "../../utils/planningReferences";
22
+ const DeferredEntityActivityTimeline = React.lazy(() => (import("../activity/EntityActivityTimeline").then((module) => ({
23
+ default: module.EntityActivityTimeline,
24
+ }))));
25
+ const DeferredTaskContextUpload = React.lazy(() => (import("./TaskContextUpload").then((module) => ({
26
+ default: module.TaskContextUpload,
27
+ }))));
28
+ function ActivityTimelineFallback({ count }) {
29
+ const placeholderCount = Math.max(2, Math.min(count || 3, 4));
30
+ return (_jsx("div", { className: `${formStyles.commentThread} ${formStyles.activityLoadingThread}`.trim(), "aria-busy": "true", "aria-live": "polite", "aria-label": "Loading activity", children: Array.from({ length: placeholderCount }, (_, index) => (_jsxs("div", { className: formStyles.activitySkeletonItem, children: [_jsx("div", { className: formStyles.activitySkeletonMeta }), _jsx("div", { className: formStyles.activitySkeletonBody })] }, index))) }));
31
+ }
23
32
  function SortableChecklistRow({ id, item, onToggle, onRemove, }) {
24
33
  const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id });
25
34
  const style = {
@@ -31,12 +40,6 @@ function SortableChecklistRow({ id, item, onToggle, onRemove, }) {
31
40
  ? "Mark checklist item incomplete"
32
41
  : "Mark checklist item complete", children: item.isCompleted ? _jsx(Check, { size: 14, strokeWidth: 2.1 }) : null }), _jsx("span", { className: `${formStyles.checklistItemText} ${item.isCompleted ? formStyles.checklistItemTextCompleted : ""}`.trim(), children: item.title }), _jsx("button", { type: "button", className: formStyles.checklistRemoveBtn, onClick: onRemove, title: "Remove checklist item", "aria-label": "Remove checklist item", children: _jsx(X, { size: 15, strokeWidth: 1.9 }) })] }));
33
42
  }
34
- function hexToRgba(hex, opacity) {
35
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
36
- if (!result)
37
- return "";
38
- return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${opacity})`;
39
- }
40
43
  export function TaskForm(props) {
41
44
  const { editingTaskId, error, title, description, category, type, priority, complexity, manualComplexityEnabled = false, assignee, scheduledDate, dueDate, workstreamInput = "", checklistItems, comments, newCommentText, contextFiles, currentWorkspaceId, apiBaseUrl, descriptionFocused, showMarkdownHelp, showChecklist = false, checklistEnabled = true, formTaxonomies, onTaxonomyChange, onOpenSettings, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions, taxonomies, workstreams = [], initiatives = [], copiedId, onTitleChange, onDescriptionChange, onCategoryChange, onTypeChange, onPriorityChange, onComplexityChange, onAssigneeChange, onScheduledDateChange, onDueDateChange, onWorkstreamInputChange = () => { }, onChecklistItemsChange, onNewCommentTextChange, onDescriptionFocusedChange, onShowMarkdownHelpChange, onShowChecklistChange = () => { }, onSubmit, onAddComment, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onOpenTaskById, currentTask, commentsEndRef, } = props;
42
45
  const currentTaskReferenceLabel = getTaskReferenceLabel(currentTask);
@@ -381,65 +384,12 @@ export function TaskForm(props) {
381
384
  const frame = window.requestAnimationFrame(scrollToLatest);
382
385
  return () => window.cancelAnimationFrame(frame);
383
386
  }, [activityCount, commentsEndRef, currentTask?.id, editingTaskId]);
384
- const activitySidebar = editingTaskId ? (_jsxs("aside", { className: formStyles.activitySidebar, "aria-label": t("taskForm.commentsSectionTitle"), children: [_jsxs("div", { className: formStyles.activitySidebarHeader, children: [_jsx("h2", { className: formStyles.activitySidebarTitle, children: t("taskForm.commentsSectionTitle") }), _jsx("span", { className: formStyles.activitySidebarCount, children: activityCount })] }), _jsxs("div", { className: `${formStyles.commentPanel} ${formStyles.activitySidebarPanel}`.trim(), children: [_jsxs("div", { className: formStyles.commentThread, children: [activityCount === 0 && (_jsx("div", { className: formStyles.emptyComments, children: t("taskForm.noComments") })), taskActivityItems.map((item) => {
385
- if (item.type === "comment") {
386
- const c = item.comment;
387
- const actorPresentation = resolveActivityActorPresentation({
388
- actorId: c.author,
389
- actorType: c.actor?.kind === "ai"
390
- ? "ai"
391
- : c.actor?.kind === "human"
392
- ? "human"
393
- : null,
394
- actorProfile: c.actor,
395
- });
396
- const commentKind = actorPresentation.kind === "agent" ? "agent" : "member";
397
- const { ActorIcon: CommentActorIcon } = actorPresentation;
398
- const authorColor = actorPresentation.color;
399
- const authorLabel = actorPresentation.label;
400
- const commentAccentStyle = authorColor && hexToRgba(authorColor, 0.35)
401
- ? {
402
- background: `linear-gradient(135deg, ${hexToRgba(authorColor, 0.18)} 0%, ${hexToRgba(authorColor, 0.1)} 100%)`,
403
- borderColor: hexToRgba(authorColor, 0.35),
404
- }
405
- : undefined;
406
- return (_jsxs("div", { className: `${formStyles.comment} ${commentKind === "agent" ? formStyles.commentAi : formStyles.commentUser}`, children: [_jsxs("div", { className: formStyles.commentHeader, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(CommentActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(c.timestamp, {
407
- month: "short",
408
- day: "numeric",
409
- hour: "2-digit",
410
- minute: "2-digit",
411
- }) })] }), _jsx("div", { className: formStyles.commentText, style: commentAccentStyle, children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: c.text }) })] }, c.id));
412
- }
413
- const event = item.event;
414
- const actorPresentation = resolveActivityActorPresentation({
415
- actorId: event.actor,
416
- actorType: event.actorType,
417
- actorProfile: event.actorProfile,
418
- fallbackKind: "system",
419
- });
420
- const { ActorIcon: EventActorIcon } = actorPresentation;
421
- const authorColor = actorPresentation.color;
422
- const authorLabel = actorPresentation.label;
423
- const eventKind = actorPresentation.kind;
424
- const changeEntries = Object.entries(event.details?.changes || {});
425
- const primaryChangeField = getTaskEventPrimaryChangeField(event);
426
- const displayedChangeEntries = changeEntries.length > 1 && primaryChangeField
427
- ? changeEntries.filter(([field]) => field !== primaryChangeField)
428
- : changeEntries.length > 1
429
- ? changeEntries
430
- : [];
431
- return (_jsxs("div", { className: `${formStyles.comment} ${formStyles.activityEvent} ${eventKind === "system" ? formStyles.activityEventSystem : ""}`, children: [_jsxs("div", { className: `${formStyles.commentHeader} ${formStyles.activityEventHeader}`, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(EventActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(event.createdAt, {
432
- month: "short",
433
- day: "numeric",
434
- hour: "2-digit",
435
- minute: "2-digit",
436
- }) })] }), _jsxs("div", { className: formStyles.activityEventText, children: [_jsx("div", { children: summarizeTaskEvent(event, formatActivityFieldValue) }), displayedChangeEntries.length > 0 && (_jsx("div", { className: formStyles.activityEventChanges, children: displayedChangeEntries.map(([field, change]) => (_jsx("div", { className: formStyles.activityEventChange, children: summarizeTaskChange(field, change, formatActivityFieldValue) }, field))) }))] })] }, item.id));
437
- }), _jsx("div", { ref: commentsEndRef })] }), _jsxs("div", { className: formStyles.commentInputArea, children: [_jsx("textarea", { className: formStyles.commentInput, placeholder: t("taskForm.commentPlaceholder"), value: newCommentText, onChange: (e) => onNewCommentTextChange(e.target.value), onKeyDown: (e) => {
387
+ const activitySidebar = editingTaskId ? (_jsxs("aside", { className: formStyles.activitySidebar, "aria-label": t("taskForm.commentsSectionTitle"), children: [_jsxs("div", { className: formStyles.activitySidebarHeader, children: [_jsx("h2", { className: formStyles.activitySidebarTitle, children: t("taskForm.commentsSectionTitle") }), _jsx("span", { className: formStyles.activitySidebarCount, children: activityCount })] }), _jsxs("div", { className: `${formStyles.commentPanel} ${formStyles.activitySidebarPanel}`.trim(), children: [_jsx(React.Suspense, { fallback: _jsx(ActivityTimelineFallback, { count: activityCount }), children: _jsx(DeferredEntityActivityTimeline, { activity: currentTask?.activity, comments: comments, formatFieldValue: formatActivityFieldValue, resolveActorPresentation: resolveActivityActorPresentation, onOpenTaskById: onOpenTaskById, emptyMessage: t("taskForm.noComments"), listClassName: formStyles.commentThread, emptyClassName: formStyles.emptyComments, endRef: commentsEndRef }) }), _jsxs("div", { className: formStyles.commentInputArea, children: [_jsx("textarea", { className: formStyles.commentInput, placeholder: t("taskForm.commentPlaceholder"), value: newCommentText, onChange: (e) => onNewCommentTextChange(e.target.value), onKeyDown: (e) => {
438
388
  if (e.key === "Enter" && !e.shiftKey) {
439
389
  e.preventDefault();
440
390
  onAddComment();
441
391
  }
442
- }, rows: 1 }), _jsx("button", { type: "button", className: formStyles.sendCommentBtn, onClick: onAddComment, disabled: !newCommentText.trim(), "aria-label": "Send comment", title: "Send comment", children: _jsx(Send, { size: 16 }) })] })] })] })) : null;
392
+ }, rows: 4 }), _jsx("button", { type: "button", className: formStyles.sendCommentBtn, onClick: onAddComment, disabled: !newCommentText.trim(), "aria-label": "Send comment", title: "Send comment", children: _jsx(Send, { size: 16 }) })] })] })] })) : null;
443
393
  return (_jsxs("form", { ref: formRef, onSubmit: onSubmit, className: `${styles.form} ${styles.appScrollbar} tf-scrollbar`, children: [error && _jsx("div", { className: formStyles.error, children: error }), isReadOnly && (_jsx("div", { className: `${formStyles.formNotice} tf-text-helper`, children: isDeletedReadOnly
444
394
  ? "Deleted tasks are read-only. Restore the task to make changes."
445
395
  : "Archived tasks are read-only. Unarchive the task to make changes." })), _jsxs("fieldset", { disabled: isReadOnly, className: `${formStyles.formFieldsetReset} ${editingTaskId ? formStyles.formLayoutWithSidebar : formStyles.formLayout}`.trim(), children: [_jsxs("div", { className: formStyles.formMainColumn, children: [_jsxs("div", { className: styles.field, children: [_jsxs("div", { className: styles.labelRow, children: [_jsx("label", { className: styles.label, children: "Title" }), assignee && (_jsx("span", { title: `Assigned to: ${currentAssigneeLabel}`, className: [
@@ -560,7 +510,7 @@ export function TaskForm(props) {
560
510
  .map((entry, entryIndex) => ({
561
511
  ...entry,
562
512
  order: entryIndex,
563
- }))) }, item.id || `checklist-${index}`))) }) })) : (_jsx("div", { className: `${formStyles.settingsHint} tf-text-helper`, children: "No checklist items yet." }))] }))] }))] }), _jsxs("div", { children: [_jsx(TaskContextUpload, { taskId: editingTaskId, taskReferenceLabel: currentTaskReferenceLabel, workspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption }), _jsx("div", { className: `${formStyles.taskFormLifecycle} ${formStyles.taskFormMetaDivider}`, children: _jsx("div", { className: formStyles.taskFormMetaGrid, children: currentTask && (_jsxs(_Fragment, { children: [_jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.createdLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: createdLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: createdByLabel
513
+ }))) }, item.id || `checklist-${index}`))) }) })) : (_jsx("div", { className: `${formStyles.settingsHint} tf-text-helper`, children: "No checklist items yet." }))] }))] }))] }), _jsxs("div", { children: [_jsx(React.Suspense, { fallback: null, children: _jsx(DeferredTaskContextUpload, { taskId: editingTaskId, taskReferenceLabel: currentTaskReferenceLabel, workspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption }) }), _jsx("div", { className: `${formStyles.taskFormLifecycle} ${formStyles.taskFormMetaDivider}`, children: _jsx("div", { className: formStyles.taskFormMetaGrid, children: currentTask && (_jsxs(_Fragment, { children: [_jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.createdLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: createdLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: createdByLabel
564
514
  ? `by ${createdByLabel}`
565
515
  : t("taskForm.emptyValue") })] })] }), _jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.updatedLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: updatedLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: updatedByLabel
566
516
  ? `by ${updatedByLabel}`
@@ -520,20 +520,12 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
520
520
  void onAssignTaskToWorkstream?.(activeTaskDataId, overWorkstreamDataId);
521
521
  return;
522
522
  }
523
- if (overType === 'planning-task-unlink' && activeTaskDataId) {
524
- void onAssignTaskToWorkstream?.(activeTaskDataId, null);
525
- return;
526
- }
527
523
  }
528
524
  if (activeType === 'planning-workstream') {
529
525
  if (overType === 'planning-initiative-target' && activeWorkstreamDataId && overInitiativeDataId) {
530
526
  void onAssignWorkstreamToInitiative?.(activeWorkstreamDataId, overInitiativeDataId);
531
527
  return;
532
528
  }
533
- if (overType === 'planning-workstream-unlink' && activeWorkstreamDataId) {
534
- void onAssignWorkstreamToInitiative?.(activeWorkstreamDataId, null);
535
- return;
536
- }
537
529
  return;
538
530
  }
539
531
  const activeId = String(active.id);
@@ -11,6 +11,7 @@ import { getVisibleTaxonomiesForTasks } from '../../utils/taxonomyLifecycle.js';
11
11
  import { TaskCard } from './TaskCard';
12
12
  import { t } from '../../localization';
13
13
  import { DeleteAllDeletedAction, TaskScopeToggle } from '../views/panels/FilterToolbarControls';
14
+ import { normalizeTaskReferenceToken } from '../../utils/taskSearch';
14
15
  export const TaskList = forwardRef((props, ref) => {
15
16
  const { tasks, archivedTasks, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions = [], workstreams = [], initiatives = [], searchQuery, filterCategories, filterTypes, filterPriorities, filterStatus, filterAssignees = [], filterTaxonomies, sortBy, sortOrder, showArchive, taskScope, collapsedCategories, loadingTasks, filteredTasks, filteredArchive, groupedTasks, filteredDeletedTasks = [], groupedDeletedTasks = {}, copiedId, recentlyChangedTaskIds = [], showTaskCardStatusLabel = true, onSearchChange, onFilterCategoriesChange, onFilterTypesChange, onFilterPrioritiesChange, onFilterStatusChange, onFilterAssigneesChange, onTaxonomyFilterChange, onSortByChange, onSortOrderChange, onShowArchiveChange, onTaskScopeChange, onClearFilters, onToggleCategory, onEditTask, onOpenTaskById, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onBulkArchive, onUnarchive, onDelete, onDeleteAllDeleted, onFetchArchive, onAddTaskToCategory, supplementalTasks = [], taxonomies, } = props;
16
17
  const changedTaskIdSet = new Set(recentlyChangedTaskIds);
@@ -46,8 +47,11 @@ export const TaskList = forwardRef((props, ref) => {
46
47
  : effectiveTaskScope === 'archived'
47
48
  ? 'archived'
48
49
  : null;
50
+ const searchedTaskReference = normalizeTaskReferenceToken(searchQuery);
49
51
  const emptyLabel = searchQuery
50
- ? t('taskList.noMatchingTasks')
52
+ ? searchedTaskReference
53
+ ? `No task found for ${searchedTaskReference.toUpperCase()}`
54
+ : t('taskList.noMatchingTasks')
51
55
  : effectiveTaskScope === 'archived'
52
56
  ? 'No archived tasks found.'
53
57
  : effectiveTaskScope === 'deleted'
@@ -390,6 +390,10 @@ describe('TaskList Component', () => {
390
390
  renderComponent({ filteredTasks: [], loadingTasks: false });
391
391
  expect(screen.getByText('No active tasks')).toBeInTheDocument();
392
392
  });
393
+ it('shows a task-reference-specific empty state for unmatched searches', () => {
394
+ renderComponent({ filteredTasks: [], loadingTasks: false, searchQuery: ' T - 123 ' });
395
+ expect(screen.getByText('No task found for T-123')).toBeInTheDocument();
396
+ });
393
397
  it('2.13 Loading state', () => {
394
398
  renderComponent({ loadingTasks: true });
395
399
  expect(screen.queryByText('No active tasks')).not.toBeInTheDocument();
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ interface HierarchyActionBadgeProps {
3
+ ariaLabel: string;
4
+ children: React.ReactNode;
5
+ disabled?: boolean;
6
+ onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
7
+ title: string;
8
+ }
9
+ export declare function HierarchyActionBadge({ ariaLabel, children, disabled, onClick, title, }: HierarchyActionBadgeProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styles from '../../Taskforce.module.css';
3
+ export function HierarchyActionBadge({ ariaLabel, children, disabled = false, onClick, title, }) {
4
+ return (_jsx("button", { type: "button", className: `${styles.taskIdBadge} ${styles.taskHierarchyActionBtn}`, onClick: onClick, disabled: disabled, title: title, "aria-label": ariaLabel, children: children }));
5
+ }
@@ -4,10 +4,12 @@ interface AppShellHeaderProps {
4
4
  projectName?: string | null;
5
5
  currentWorkspaceId?: string | null;
6
6
  brandLabel?: string;
7
+ brandHomeLabel?: string;
8
+ onBrandClick?: () => void;
7
9
  runtimeMode?: 'local' | 'cloud';
8
10
  theme?: TaskforceTheme;
9
11
  meta?: React.ReactNode;
10
12
  actions?: React.ReactNode;
11
13
  }
12
- export declare function AppShellHeader({ projectName, currentWorkspaceId, brandLabel, runtimeMode, theme, meta, actions }: AppShellHeaderProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function AppShellHeader({ projectName, currentWorkspaceId, brandLabel, brandHomeLabel, onBrandClick, runtimeMode, theme, meta, actions }: AppShellHeaderProps): import("react/jsx-runtime").JSX.Element;
13
15
  export {};
@@ -5,19 +5,35 @@ import logoLight from '../../assets/branding/logos/logo_light.svg';
5
5
  import logoDark from '../../assets/branding/logos/logo_dark.svg';
6
6
  import favicon from '../../assets/favicon/favicon.svg';
7
7
  import { DEFAULT_TASKFORCE_THEME, isLightThemeFamily } from '../../utils/theme';
8
- export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = 'TaskForce', runtimeMode = 'local', theme = DEFAULT_TASKFORCE_THEME, meta, actions }) {
8
+ export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = 'TaskForce', brandHomeLabel = 'Go to Kanban', onBrandClick, runtimeMode = 'local', theme = DEFAULT_TASKFORCE_THEME, meta, actions }) {
9
9
  const logo = isLightThemeFamily(theme) ? logoDark : logoLight;
10
10
  const normalizedProjectName = String(projectName || '').trim();
11
- const normalizedWorkspaceId = String(currentWorkspaceId || '').trim().toLowerCase();
11
+ const workspaceIdForCopy = String(currentWorkspaceId || '').trim();
12
+ const normalizedWorkspaceId = workspaceIdForCopy.toLowerCase();
12
13
  const showProjectName = Boolean(normalizedProjectName
13
14
  && (runtimeMode !== 'cloud' || (normalizedWorkspaceId && normalizedWorkspaceId !== 'default')));
14
- return (_jsxs("div", { className: `${styles.header} ${shellStyles.standaloneHeader}`, children: [_jsxs("div", { className: `${styles.headerTitle} ${shellStyles.standaloneTitle}`, children: [_jsx("img", { src: logo, alt: "Taskforce Logo", className: styles.brandIcon, onError: (event) => {
15
- const target = event.currentTarget;
16
- if (target.src !== favicon) {
17
- target.src = favicon;
18
- }
19
- else {
20
- target.style.display = 'none';
21
- }
22
- } }), _jsx("span", { children: brandLabel }), runtimeMode === 'cloud' && (_jsx("span", { className: styles.brandCloudSuffix, children: "HQ" })), showProjectName && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.projectSlash, children: "/" }), _jsx("span", { className: styles.projectName, title: currentWorkspaceId ? `Workspace ID: ${currentWorkspaceId}` : undefined, children: normalizedProjectName })] })), meta] }), _jsx("div", { className: styles.headerActions, style: { gap: '16px' }, children: actions })] }));
15
+ const copyWorkspaceId = () => {
16
+ if (!workspaceIdForCopy || typeof navigator === 'undefined')
17
+ return;
18
+ const clipboard = navigator.clipboard;
19
+ if (!clipboard?.writeText)
20
+ return;
21
+ void clipboard.writeText(workspaceIdForCopy).catch(() => undefined);
22
+ };
23
+ const handleProjectNameKeyDown = (event) => {
24
+ if (event.key !== 'Enter' && event.key !== ' ')
25
+ return;
26
+ event.preventDefault();
27
+ copyWorkspaceId();
28
+ };
29
+ const brandContent = (_jsxs(_Fragment, { children: [_jsx("img", { src: logo, alt: "Taskforce Logo", className: styles.brandIcon, onError: (event) => {
30
+ const target = event.currentTarget;
31
+ if (target.src !== favicon) {
32
+ target.src = favicon;
33
+ }
34
+ else {
35
+ target.style.display = 'none';
36
+ }
37
+ } }), _jsx("span", { children: brandLabel }), runtimeMode === 'cloud' && (_jsx("span", { className: styles.brandCloudSuffix, children: "HQ" }))] }));
38
+ return (_jsxs("div", { className: `${styles.header} ${shellStyles.standaloneHeader}`, children: [_jsxs("div", { className: `${styles.headerTitle} ${shellStyles.standaloneTitle}`, children: [onBrandClick ? (_jsx("button", { type: "button", className: styles.brandHomeButton, onClick: onBrandClick, title: brandHomeLabel, "aria-label": brandHomeLabel, children: brandContent })) : (_jsx("span", { className: styles.brandHomeStatic, children: brandContent })), showProjectName && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.projectSlash, children: "/" }), _jsx("span", { className: styles.projectName, title: workspaceIdForCopy ? `Workspace ID: ${workspaceIdForCopy}` : undefined, role: workspaceIdForCopy ? 'button' : undefined, tabIndex: workspaceIdForCopy ? 0 : undefined, onClick: copyWorkspaceId, onMouseDown: (event) => event.preventDefault(), onKeyDown: handleProjectNameKeyDown, children: normalizedProjectName })] })), meta] }), _jsx("div", { className: styles.headerActions, style: { gap: '16px' }, children: actions })] }));
23
39
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render, screen } from '@testing-library/react';
3
- import { describe, expect, it } from 'vitest';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { describe, expect, it, vi } from 'vitest';
4
5
  import { AppShellHeader } from './AppShellHeader';
5
6
  import logoDark from '../../assets/branding/logos/logo_dark.svg';
6
7
  describe('AppShellHeader', () => {
@@ -18,6 +19,22 @@ describe('AppShellHeader', () => {
18
19
  render(_jsx(AppShellHeader, { runtimeMode: "local", theme: "dawn" }));
19
20
  expect(screen.getByAltText('Taskforce Logo')).toHaveAttribute('src', logoDark);
20
21
  });
22
+ it('calls the brand click handler from the logo button', async () => {
23
+ const onBrandClick = vi.fn();
24
+ render(_jsx(AppShellHeader, { runtimeMode: "local", onBrandClick: onBrandClick }));
25
+ await userEvent.click(screen.getByRole('button', { name: 'Go to Kanban' }));
26
+ expect(onBrandClick).toHaveBeenCalledTimes(1);
27
+ });
28
+ it('copies the workspace id from the project badge', async () => {
29
+ const writeText = vi.fn().mockResolvedValue(undefined);
30
+ Object.defineProperty(navigator, 'clipboard', {
31
+ configurable: true,
32
+ value: { writeText }
33
+ });
34
+ render(_jsx(AppShellHeader, { runtimeMode: "local", projectName: "Roadmap", currentWorkspaceId: "workspace-alpha" }));
35
+ await userEvent.click(screen.getByRole('button', { name: 'Roadmap' }));
36
+ expect(writeText).toHaveBeenCalledWith('workspace-alpha');
37
+ });
21
38
  it('hides a stale project label while cloud runtime is still on the default workspace', () => {
22
39
  render(_jsx(AppShellHeader, { runtimeMode: "cloud", projectName: "Old Workspace", currentWorkspaceId: "default" }));
23
40
  expect(screen.queryByText('Old Workspace')).not.toBeInTheDocument();
@@ -1,6 +1,15 @@
1
1
  import React from 'react';
2
+ import type { TaskforceTheme } from '../../utils/theme';
2
3
  export type BillingInterval = 'month' | 'year';
3
4
  export type BillingPricingType = 'stripe' | 'free';
5
+ export type BillingPricingAudience = 'public' | 'campaign';
6
+ type PublicPricingValue = {
7
+ pricingType: BillingPricingType;
8
+ stripePriceId: string | null;
9
+ unitAmount: number | null;
10
+ currency: string | null;
11
+ interval: string;
12
+ } | null;
4
13
  export interface PublicPricingPlan {
5
14
  planId: string;
6
15
  displayName: string;
@@ -20,22 +29,15 @@ export interface PublicPricingPlan {
20
29
  description?: string | null;
21
30
  publicLabel?: string | null;
22
31
  publicDescription?: string | null;
32
+ publicDescriptionVisible?: boolean | null;
23
33
  }>;
24
34
  pricing: {
25
- month: {
26
- pricingType: BillingPricingType;
27
- stripePriceId: string | null;
28
- unitAmount: number | null;
29
- currency: string | null;
30
- interval: string;
31
- } | null;
32
- year: {
33
- pricingType: BillingPricingType;
34
- stripePriceId: string | null;
35
- unitAmount: number | null;
36
- currency: string | null;
37
- interval: string;
38
- } | null;
35
+ month: PublicPricingValue;
36
+ year: PublicPricingValue;
37
+ };
38
+ campaignPricing?: {
39
+ month: PublicPricingValue;
40
+ year: PublicPricingValue;
39
41
  };
40
42
  }
41
43
  interface PricingSourceDetails {
@@ -47,12 +49,14 @@ export interface PlanSelectionIntent {
47
49
  planVersionId: string;
48
50
  interval: BillingInterval;
49
51
  pricingType: BillingPricingType;
52
+ pricingAudience: BillingPricingAudience;
50
53
  isCurrentPlan: boolean;
51
54
  }
52
55
  interface PlanComparisonPageProps {
53
56
  heading: string;
54
57
  subtitle?: string;
55
58
  variant?: 'app' | 'site';
59
+ theme?: TaskforceTheme;
56
60
  chrome?: React.ReactNode;
57
61
  footer?: React.ReactNode;
58
62
  isAuthenticated: boolean;
@@ -74,9 +78,10 @@ interface PlanComparisonPageProps {
74
78
  shellOwnsScroll?: boolean;
75
79
  showPageBackButton?: boolean;
76
80
  showHeaderBarWithChrome?: boolean;
81
+ preferPricingPageTitle?: boolean;
77
82
  currentPlanCardActionMode?: 'manage' | 'indicator' | 'hidden';
78
83
  fallbackPricingSource?: PricingSourceDetails | null;
79
84
  onSelectPlan: (intent: PlanSelectionIntent) => void | Promise<void>;
80
85
  }
81
- export declare function PlanComparisonPage({ heading, subtitle, variant, chrome, footer, isAuthenticated, authSessionResolved, currentPlanId, currentPlanVersionId, currentEntitlementState, markCurrentPlan, canManageCurrentPlan, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled, topActions, shellOwnsScroll, showPageBackButton, showHeaderBarWithChrome, currentPlanCardActionMode, fallbackPricingSource, onSelectPlan }: PlanComparisonPageProps): import("react/jsx-runtime").JSX.Element;
86
+ export declare function PlanComparisonPage({ heading, subtitle, variant, theme, chrome, footer, isAuthenticated, authSessionResolved, currentPlanId, currentPlanVersionId, currentEntitlementState, markCurrentPlan, canManageCurrentPlan, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled, topActions, shellOwnsScroll, showPageBackButton, showHeaderBarWithChrome, preferPricingPageTitle, currentPlanCardActionMode, fallbackPricingSource, onSelectPlan }: PlanComparisonPageProps): import("react/jsx-runtime").JSX.Element;
82
87
  export {};