@taskforcehq/taskforce 0.3.328 → 0.3.329

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 (264) hide show
  1. package/dist/Taskforce.module.css +233 -34
  2. package/dist/TaskforceCore.js +237 -230
  3. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
  4. package/dist/cli.js +34 -0
  5. package/dist/cliCloudTargetGuard.d.ts +5 -0
  6. package/dist/cliCloudTargetGuard.js +11 -0
  7. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  8. package/dist/components/features/TaskSettings.d.ts +12 -1
  9. package/dist/components/features/TaskSettings.js +147 -40
  10. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  11. package/dist/components/features/planning/PlanningModule.js +2 -5
  12. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  13. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  14. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  15. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  16. package/dist/components/features/taskforceAgentEditorModel.js +3 -1
  17. package/dist/components/task/TaskCard.d.ts +5 -0
  18. package/dist/components/task/TaskCard.js +65 -10
  19. package/dist/components/task/TaskImageReviews.js +14 -5
  20. package/dist/components/task/TaskKanban.d.ts +6 -1
  21. package/dist/components/task/TaskKanban.js +152 -6
  22. package/dist/components/task/TaskList.d.ts +5 -0
  23. package/dist/components/task/TaskList.js +3 -3
  24. package/dist/components/ui/PasswordInput.d.ts +6 -0
  25. package/dist/components/ui/PasswordInput.js +11 -0
  26. package/dist/components/ui/TopNoticeLayer.js +1 -1
  27. package/dist/components/views/AppShellHeader.js +32 -11
  28. package/dist/components/views/PlanComparisonPage.js +12 -0
  29. package/dist/components/views/StandaloneLayout.js +7 -31
  30. package/dist/components/views/WidgetView.js +3 -3
  31. package/dist/components/views/panels/PlanningDrawer.js +5 -12
  32. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  33. package/dist/config/envSchema.js +12 -0
  34. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  35. package/dist/config/localServiceRuntimeContract.js +135 -0
  36. package/dist/core/AiProfileService.js +46 -8
  37. package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
  38. package/dist/core/DeletedTaskLifecycleService.js +37 -6
  39. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
  40. package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
  41. package/dist/core/Taskforce.d.ts +62 -2
  42. package/dist/core/Taskforce.js +635 -32
  43. package/dist/core/types.d.ts +6 -0
  44. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  45. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  46. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  47. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  48. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  49. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  50. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  51. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  52. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  53. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  54. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  55. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  56. package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
  57. package/dist/hooks/tasks/useTaskFormActions.js +65 -12
  58. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  59. package/dist/hooks/ui/useSettingsModel.js +13 -2
  60. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  61. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  62. package/dist/hooks/useFilterSort.d.ts +7 -3
  63. package/dist/hooks/useFilterSort.js +15 -5
  64. package/dist/hooks/useTaskData.d.ts +10 -1
  65. package/dist/hooks/useTaskData.js +216 -7
  66. package/dist/hooks/useTaskMutations.d.ts +10 -2
  67. package/dist/hooks/useTaskMutations.js +223 -43
  68. package/dist/hooks/useTaskforce.d.ts +37 -9
  69. package/dist/hooks/useTaskforce.js +349 -39
  70. package/dist/hooks/useUiNotice.d.ts +4 -0
  71. package/dist/hooks/useUiNotice.js +4 -0
  72. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  73. package/dist/mcp/documentAssetRegistrar.js +44 -35
  74. package/dist/mcp/documentHelpers.d.ts +1 -0
  75. package/dist/mcp/documentHelpers.js +7 -3
  76. package/dist/mcp/runtime.js +172 -42
  77. package/dist/mcp/taskPlanningRegistrar.js +41 -38
  78. package/dist/mcp/toolCallCompatibility.js +38 -1
  79. package/dist/mcp/toolCatalog.d.ts +1 -0
  80. package/dist/migrations/taskSchemaMigrations.js +12 -0
  81. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  82. package/dist/runtime/appGateDefinitions.js +6 -6
  83. package/dist/runtime/appGates.d.ts +12 -13
  84. package/dist/runtime/appGates.js +29 -13
  85. package/dist/runtime/nodeAppGates.d.ts +8 -0
  86. package/dist/runtime/nodeAppGates.js +27 -0
  87. package/dist/server/cloudCollectionRead.d.ts +1 -1
  88. package/dist/server/cloudCollectionRead.js +2 -2
  89. package/dist/server/index.d.ts +3 -0
  90. package/dist/server/index.js +15 -3
  91. package/dist/server/localServiceLease.d.ts +3 -0
  92. package/dist/server/localServiceLease.js +9 -1
  93. package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
  94. package/dist/server/rateLimit.d.ts +1 -0
  95. package/dist/server/rateLimit.js +4 -0
  96. package/dist/server/realtimeSyncWs.js +36 -12
  97. package/dist/server/routes/admin.js +1 -0
  98. package/dist/server/routes/agentRoles.js +3 -2
  99. package/dist/server/routes/agentSkills.js +3 -2
  100. package/dist/server/routes/agents.js +118 -16
  101. package/dist/server/routes/annotatedAttachments.js +3 -2
  102. package/dist/server/routes/auth.js +3 -1
  103. package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
  104. package/dist/server/routes/localService.js +2 -0
  105. package/dist/server/routes/primitives.d.ts +2 -0
  106. package/dist/server/routes/syncAuxRoutes.js +5 -2
  107. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  108. package/dist/server/routes/syncV3FeedRoutes.js +18 -16
  109. package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
  110. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  111. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  112. package/dist/server/routes/syncV3OperationRoutes.js +10 -0
  113. package/dist/server/routes/tasks.js +103 -11
  114. package/dist/server/routes/workspaces.js +43 -1
  115. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  116. package/dist/server/workspaceDeletionAdmission.js +50 -0
  117. package/dist/service/localServiceCli.js +16 -0
  118. package/dist/service/localServiceClientLifecycle.js +36 -10
  119. package/dist/services/codexAppServerModelGateway.d.ts +112 -0
  120. package/dist/services/codexAppServerModelGateway.js +438 -0
  121. package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
  122. package/dist/services/taskforceAgentMcpBridge.js +23 -3
  123. package/dist/shared/taskforceAgentDefinition.js +7 -1
  124. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  125. package/dist/shared/taskforceAgentModels.js +30 -1
  126. package/dist/storage/postgresAdapter.d.ts +20 -3
  127. package/dist/storage/postgresAdapter.js +106 -14
  128. package/dist/storage/postgresWorker.js +13 -4
  129. package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
  130. package/dist/storage/postgresWorkerProtocol.js +30 -0
  131. package/dist/sync/cloudSyncApi.d.ts +4 -1
  132. package/dist/sync/cloudSyncApi.js +7 -2
  133. package/dist/sync/contentSyncState.d.ts +1 -1
  134. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
  135. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
  136. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  137. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  138. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  139. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  140. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
  141. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  142. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
  143. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  144. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
  145. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  146. package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
  147. package/dist/sync/syncService.d.ts +2 -0
  148. package/dist/sync/syncService.js +4 -1
  149. package/dist/sync/v3/combinedFeedCapability.js +4 -0
  150. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
  151. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
  152. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
  153. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
  154. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  155. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
  156. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  157. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
  158. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
  159. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
  160. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  161. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  162. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  163. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  164. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  165. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  166. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  167. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  168. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  169. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
  170. package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
  171. package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
  172. package/dist/sync/v3/syncDurabilityStore.js +31 -11
  173. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
  174. package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
  175. package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
  176. package/dist/sync/v3/taskChecklistMutationService.js +4 -0
  177. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  178. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  179. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  180. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  181. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  182. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  183. package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
  184. package/dist/sync/v3/taskStatusMutationService.js +4 -0
  185. package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
  186. package/dist/sync/v3/workflowAssignmentSync.js +3 -0
  187. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  188. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
  189. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  190. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  191. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  192. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  193. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
  194. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
  195. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
  196. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
  197. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
  198. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
  199. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
  200. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
  201. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
  202. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
  203. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
  204. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
  205. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  206. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  207. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  208. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  209. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  210. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  211. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  212. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  213. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  214. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  215. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  216. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
  217. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
  218. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
  219. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  220. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  221. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  222. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  223. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  224. package/dist/sync/workspacePullFeed.js +2 -1
  225. package/dist/sync/workspaceRepair.d.ts +3 -0
  226. package/dist/sync/workspaceRepair.js +67 -6
  227. package/dist/types.d.ts +47 -1
  228. package/dist/types.js +6 -0
  229. package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
  230. package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
  231. package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
  232. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  233. package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
  234. package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
  235. package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
  236. package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
  237. package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
  238. package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
  239. package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
  240. package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
  241. package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
  242. package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
  243. package/dist/ui/assets/index-I5zhdtgt.js +7 -0
  244. package/dist/ui/assets/index-VRFsx3wz.css +1 -0
  245. package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
  246. package/dist/ui/index.html +3 -3
  247. package/dist/utils/taskActivity.d.ts +3 -2
  248. package/dist/utils/taskActivity.js +19 -2
  249. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  250. package/dist/utils/taskEditorReconciliation.js +217 -0
  251. package/dist/utils/taskNormalization.d.ts +1 -0
  252. package/dist/utils/taskNormalization.js +39 -0
  253. package/dist/utils/taskSearch.js +2 -2
  254. package/dist/utils/uiNotice.d.ts +4 -0
  255. package/package.json +16 -4
  256. package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
  257. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  258. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
  259. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
  260. package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
  261. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
  262. package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
  263. package/dist/ui/assets/index-BUplSsv_.js +0 -7
  264. package/dist/ui/assets/index-gz2EXuoK.css +0 -1
@@ -16,7 +16,7 @@ import { AssigneeIdentity } from '../ui/AssigneeIdentity';
16
16
  const EMPTY_DELETED_RECORD_MAP = new Map();
17
17
  const EMPTY_SELECTED_DELETED_IDS = new Set();
18
18
  export const TaskList = forwardRef((props, ref) => {
19
- const { tasks, archivedTasks, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions = [], workstreams = [], initiatives = [], workspaceId = null, searchQuery, filterCategories, filterTypes, filterPriorities, filterStatus, filterAssignees = [], filterTaxonomies, sortBy, sortOrder, showArchive, taskScope, collapsedCategories, loadingTasks, filteredTasks, filteredArchive, groupedTasks, filteredDeletedTasks = [], groupedDeletedTasks = {}, copiedId, recentlyChangedTaskIds = [], showTaskCardStatusLabel = false, deletedTaskRecordByTaskId = EMPTY_DELETED_RECORD_MAP, selectedDeletedRecordIds = EMPTY_SELECTED_DELETED_IDS, onDeletedSelectionChange, trashControls, onSearchChange, onFilterCategoriesChange, onFilterTypesChange, onFilterPrioritiesChange, onFilterStatusChange, onFilterAssigneesChange, onTaxonomyFilterChange, onSortByChange, onSortOrderChange, onShowArchiveChange, onTaskScopeChange, onClearFilters, onToggleCategory, onEditTask, onUpdateTask, taskReferences, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onBulkArchive, onUnarchive, onDelete, onFetchArchive, onAddTaskToCategory, supplementalTasks = [], taxonomies, } = props;
19
+ const { tasks, archivedTasks, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions = [], workstreams = [], initiatives = [], workspaceId = null, searchQuery, filterCategories, filterTypes, filterPriorities, filterStatus, filterAssignees = [], filterTaxonomies, sortBy, sortOrder, showArchive, taskScope, collapsedCategories, loadingTasks, filteredTasks, filteredArchive, groupedTasks, filteredDeletedTasks = [], groupedDeletedTasks = {}, copiedId, recentlyChangedTaskIds = [], showTaskCardStatusLabel = false, showTaskCardDescription = true, showTaskCardAttachments = true, showTaskCardChecklistProgress = true, showTaskCardLatestActivity = true, tintTaskCardActivityBackground = false, deletedTaskRecordByTaskId = EMPTY_DELETED_RECORD_MAP, selectedDeletedRecordIds = EMPTY_SELECTED_DELETED_IDS, onDeletedSelectionChange, trashControls, onSearchChange, onFilterCategoriesChange, onFilterTypesChange, onFilterPrioritiesChange, onFilterStatusChange, onFilterAssigneesChange, onTaxonomyFilterChange, onSortByChange, onSortOrderChange, onShowArchiveChange, onTaskScopeChange, onClearFilters, onToggleCategory, onEditTask, onUpdateTask, taskReferences, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onBulkArchive, onUnarchive, onDelete, onFetchArchive, onAddTaskToCategory, supplementalTasks = [], taxonomies, } = props;
20
20
  const changedTaskIdSet = React.useMemo(() => new Set(recentlyChangedTaskIds), [recentlyChangedTaskIds]);
21
21
  const workstreamById = React.useMemo(() => new Map(workstreams.map((workstream) => [workstream.id, workstream])), [workstreams]);
22
22
  const initiativeById = React.useMemo(() => new Map(initiatives.map((initiative) => [initiative.id, initiative])), [initiatives]);
@@ -132,11 +132,11 @@ export const TaskList = forwardRef((props, ref) => {
132
132
  : undefined;
133
133
  return (_jsx(TaskCard, { task: task, taskWorkstream: taskWorkstream, taskInitiative: taskInitiative, workspaceId: workspaceId, searchQuery: searchQuery, copiedId: copiedId, taxonomies: taxonomies, types: types, priorities: priorities, assigneeOptions: assigneeOptions, workstreams: workstreams, onClick: onEditTask, onAssigneeChange: handleAssigneeChange, onWorkstreamChange: (nextTask, workstreamId) => (onUpdateTask(nextTask.id, { workstreamId })), taskReferences: taskReferences, onCopyId: onCopyId, onToggleInProgress: onToggleInProgress, onToggleReview: onToggleReview, onToggleComplete: onToggleComplete, onToggleCancel: onToggleCancel, onSetStatus: onSetStatus, onArchiveTask: onArchiveTask, onUnarchive: activeReadOnlyMode ? onUnarchive : undefined, onDelete: activeReadOnlyMode ? onDelete : undefined, categories: categories, isRecentlyChanged: changedTaskIdSet.has(task.id), readOnlyMode: activeReadOnlyMode, selectable: Boolean(deletedRecord && onDeletedSelectionChange), selected: Boolean(deletedRecord && selectedDeletedRecordIds.has(deletedRecord.id)), onSelectionChange: deletedRecord
134
134
  ? (selected) => onDeletedSelectionChange?.(deletedRecord.id, selected)
135
- : undefined, showStatusLabel: showTaskCardStatusLabel }, task.id));
135
+ : undefined, showStatusLabel: showTaskCardStatusLabel, showDescription: showTaskCardDescription, showAttachments: showTaskCardAttachments, showChecklistProgress: showTaskCardChecklistProgress, showLatestActivity: showTaskCardLatestActivity, tintActivityBackground: tintTaskCardActivityBackground }, task.id));
136
136
  })())) }))] }, category));
137
137
  }) })), !useScopeToggle && showArchive && filteredArchive.length > 0 && (_jsxs("div", { className: styles.archiveList, children: [_jsx("div", { className: styles.archiveHeader, children: "Archived" }), filteredArchive.map((task) => ((() => {
138
138
  const { taskWorkstream, taskInitiative } = resolveTaskHierarchy(task);
139
- return (_jsx(TaskCard, { task: task, taskWorkstream: taskWorkstream, taskInitiative: taskInitiative, workspaceId: workspaceId, searchQuery: searchQuery, copiedId: copiedId, isArchived: true, types: types, assigneeOptions: assigneeOptions, workstreams: workstreams, onClick: onEditTask, onCopyId: onCopyId, onSetStatus: onSetStatus, onUnarchive: onUnarchive, onDelete: onDelete, deleteActionTitle: "Delete Permanently", taskReferences: taskReferences, categories: categories, showStatusLabel: showTaskCardStatusLabel }, task.id));
139
+ return (_jsx(TaskCard, { task: task, taskWorkstream: taskWorkstream, taskInitiative: taskInitiative, workspaceId: workspaceId, searchQuery: searchQuery, copiedId: copiedId, isArchived: true, types: types, assigneeOptions: assigneeOptions, workstreams: workstreams, onClick: onEditTask, onCopyId: onCopyId, onSetStatus: onSetStatus, onUnarchive: onUnarchive, onDelete: onDelete, deleteActionTitle: "Delete Permanently", taskReferences: taskReferences, categories: categories, showStatusLabel: showTaskCardStatusLabel, showDescription: showTaskCardDescription, showAttachments: showTaskCardAttachments, showChecklistProgress: showTaskCardChecklistProgress, showLatestActivity: showTaskCardLatestActivity, tintActivityBackground: tintTaskCardActivityBackground }, task.id));
140
140
  })()))] }))] }));
141
141
  });
142
142
  TaskList.displayName = 'TaskList';
@@ -0,0 +1,6 @@
1
+ import React, { type InputHTMLAttributes } from 'react';
2
+ export interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
3
+ visibilityLabel?: string;
4
+ visibilityToggleTestId?: string;
5
+ }
6
+ export declare const PasswordInput: React.ForwardRefExoticComponent<PasswordInputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useState } from 'react';
3
+ import { Eye, EyeOff } from 'lucide-react';
4
+ import styles from './PasswordInput.module.css';
5
+ export const PasswordInput = forwardRef(function PasswordInput({ className, disabled, visibilityLabel = 'password', visibilityToggleTestId, ...inputProps }, ref) {
6
+ const [visible, setVisible] = useState(false);
7
+ const actionLabel = `${visible ? 'Hide' : 'Show'} ${visibilityLabel}`;
8
+ return (_jsxs("span", { className: styles.wrapper, children: [_jsx("input", { ...inputProps, ref: ref, className: `${className || ''} ${styles.input}`.trim(), type: visible ? 'text' : 'password', disabled: disabled }), _jsx("button", { type: "button", className: styles.toggle, "aria-label": actionLabel, title: actionLabel, "data-testid": visibilityToggleTestId, disabled: disabled, onClick: () => setVisible((current) => !current), children: visible
9
+ ? _jsx(EyeOff, { size: 18, "aria-hidden": "true" })
10
+ : _jsx(Eye, { size: 18, "aria-hidden": "true" }) })] }));
11
+ });
@@ -13,5 +13,5 @@ export function TopNoticeLayer({ notice, onDismiss, placement = 'top' }) {
13
13
  const layerClassName = placement === 'task'
14
14
  ? `${styles.topNoticeLayer} ${styles.taskNoticeLayer}`
15
15
  : styles.topNoticeLayer;
16
- return (_jsx("div", { className: layerClassName, children: _jsxs("div", { className: `${styles.topNotice} ${toneClass}`, role: statusRole, "aria-live": notice.tone === 'error' ? 'assertive' : 'polite', children: [notice.tone === 'error' ? _jsx(AlertTriangle, { size: 14 }) : notice.tone === 'success' ? _jsx(Check, { size: 14 }) : _jsx(Info, { size: 14 }), _jsx("span", { className: styles.topNoticeMessage, children: notice.message }), notice.actionLabel && notice.onAction && (_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: notice.onAction, children: notice.actionLabel })), onDismiss && (_jsx("button", { type: "button", className: styles.topNoticeDismiss, onClick: onDismiss, "aria-label": "Dismiss notice", title: "Dismiss notice", children: _jsx(X, { size: 14 }) }))] }) }));
16
+ return (_jsx("div", { className: layerClassName, children: _jsxs("div", { className: `${styles.topNotice} ${toneClass}`, role: statusRole, "aria-live": notice.tone === 'error' ? 'assertive' : 'polite', children: [notice.tone === 'error' ? _jsx(AlertTriangle, { size: 14 }) : notice.tone === 'success' ? _jsx(Check, { size: 14 }) : _jsx(Info, { size: 14 }), _jsx("span", { className: styles.topNoticeMessage, children: notice.message }), notice.actionLabel && notice.onAction && (_jsx("button", { type: "button", className: `${notice.actionPrimary ? 'tf-button-primary' : 'tf-button-ghost'} tf-button-compact`, onClick: notice.onAction, children: notice.actionLabel })), notice.secondaryActionLabel && notice.onSecondaryAction && (_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: notice.onSecondaryAction, children: notice.secondaryActionLabel })), onDismiss && notice.dismissible !== false && (_jsx("button", { type: "button", className: styles.topNoticeDismiss, onClick: onDismiss, "aria-label": "Dismiss notice", title: "Dismiss notice", children: _jsx(X, { size: 14 }) }))] }) }));
17
17
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useLayoutEffect, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
3
  import styles from '../../Taskforce.module.css';
4
4
  import shellStyles from './WorkspaceShell.module.css';
5
5
  import logoLight from '../../assets/branding/logos/logo_light_no_bg.svg';
@@ -10,6 +10,7 @@ export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = '
10
10
  const headerTitleRef = useRef(null);
11
11
  const projectNameGroupRef = useRef(null);
12
12
  const [projectNameFits, setProjectNameFits] = useState(true);
13
+ const [workspaceCopyStatus, setWorkspaceCopyStatus] = useState('idle');
13
14
  const logo = isLightThemeFamily(theme) ? logoDark : logoLight;
14
15
  const normalizedProjectName = String(projectName || '').trim();
15
16
  const workspaceIdForCopy = String(currentWorkspaceId || '').trim();
@@ -56,20 +57,28 @@ export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = '
56
57
  resizeObserver.observe(projectEl);
57
58
  return () => resizeObserver.disconnect();
58
59
  }, [measureProjectNameFit, normalizedProjectName, meta]);
60
+ useEffect(() => {
61
+ if (workspaceCopyStatus === 'idle')
62
+ return;
63
+ const timer = window.setTimeout(() => setWorkspaceCopyStatus('idle'), 1600);
64
+ return () => window.clearTimeout(timer);
65
+ }, [workspaceCopyStatus]);
59
66
  const showProjectName = canShowProjectName && projectNameFits;
60
- const copyWorkspaceId = () => {
67
+ const copyWorkspaceId = async () => {
61
68
  if (!workspaceIdForCopy || typeof navigator === 'undefined')
62
69
  return;
63
70
  const clipboard = navigator.clipboard;
64
- if (!clipboard?.writeText)
65
- return;
66
- void clipboard.writeText(workspaceIdForCopy).catch(() => undefined);
67
- };
68
- const handleProjectNameKeyDown = (event) => {
69
- if (event.key !== 'Enter' && event.key !== ' ')
71
+ if (!clipboard?.writeText) {
72
+ setWorkspaceCopyStatus('failed');
70
73
  return;
71
- event.preventDefault();
72
- copyWorkspaceId();
74
+ }
75
+ try {
76
+ await clipboard.writeText(workspaceIdForCopy);
77
+ setWorkspaceCopyStatus('copied');
78
+ }
79
+ catch {
80
+ setWorkspaceCopyStatus('failed');
81
+ }
73
82
  };
74
83
  const brandContent = (_jsxs(_Fragment, { children: [_jsx("img", { src: logo, alt: "Taskforce Logo", className: styles.brandIcon, onError: (event) => {
75
84
  const target = event.currentTarget;
@@ -80,5 +89,17 @@ export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = '
80
89
  target.style.display = 'none';
81
90
  }
82
91
  } }), _jsx("span", { children: brandLabel }), runtimeMode === 'cloud' && (_jsx("span", { className: styles.brandCloudSuffix, children: "HQ" }))] }));
83
- return (_jsxs("div", { className: `${styles.header} ${shellStyles.standaloneHeader}`, children: [_jsxs("div", { ref: headerTitleRef, 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 })), canShowProjectName && (_jsxs("span", { ref: projectNameGroupRef, className: `${styles.projectNameGroup} ${showProjectName ? '' : styles.projectNameGroupHidden}`.trim(), "aria-hidden": showProjectName ? undefined : true, children: [_jsx("span", { className: styles.projectSlash, children: "/" }), _jsx("span", { className: styles.projectName, title: showProjectName && workspaceIdForCopy ? `Workspace ID: ${workspaceIdForCopy}` : undefined, role: showProjectName && workspaceIdForCopy ? 'button' : undefined, tabIndex: showProjectName && workspaceIdForCopy ? 0 : undefined, onClick: copyWorkspaceId, onMouseDown: (event) => event.preventDefault(), onKeyDown: handleProjectNameKeyDown, children: normalizedProjectName })] })), meta] }), _jsx("div", { className: styles.headerActions, style: { gap: '16px' }, children: actions })] }));
92
+ return (_jsxs("div", { className: `${styles.header} ${shellStyles.standaloneHeader}`, children: [_jsxs("div", { ref: headerTitleRef, 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 })), canShowProjectName && (_jsxs("span", { ref: projectNameGroupRef, className: `${styles.projectNameGroup} ${showProjectName ? '' : styles.projectNameGroupHidden}`.trim(), "aria-hidden": showProjectName ? undefined : true, children: [_jsx("span", { className: styles.projectSlash, children: "/" }), workspaceIdForCopy ? (_jsx("button", { type: "button", className: `${styles.projectName} ${workspaceCopyStatus === 'copied' ? styles.projectNameCopied : ''}`.trim(), title: workspaceCopyStatus === 'copied'
93
+ ? 'Workspace ID copied'
94
+ : workspaceCopyStatus === 'failed'
95
+ ? 'Unable to copy workspace ID'
96
+ : `Workspace ID: ${workspaceIdForCopy}`, "aria-label": `${normalizedProjectName}. ${workspaceCopyStatus === 'copied'
97
+ ? 'Workspace ID copied.'
98
+ : workspaceCopyStatus === 'failed'
99
+ ? 'Unable to copy workspace ID.'
100
+ : 'Copy workspace ID.'}`, disabled: !showProjectName, onClick: () => { void copyWorkspaceId(); }, children: normalizedProjectName })) : (_jsx("span", { className: styles.projectName, children: normalizedProjectName })), _jsx("span", { className: styles.projectNameCopyFeedback, "aria-live": "polite", children: workspaceCopyStatus === 'copied'
101
+ ? 'Workspace ID copied.'
102
+ : workspaceCopyStatus === 'failed'
103
+ ? 'Unable to copy workspace ID.'
104
+ : '' })] })), meta] }), _jsx("div", { className: styles.headerActions, style: { gap: '16px' }, children: actions })] }));
84
105
  }
@@ -147,6 +147,12 @@ function readConfiguredFeatureLimit(config, key) {
147
147
  const normalized = Math.max(1, Math.floor(numericValue));
148
148
  return normalized;
149
149
  }
150
+ /**
151
+ * Shown for count-limited features that are enabled with no configured ceiling.
152
+ * Applies to workspaces and AI profiles only — storage keeps an explicit
153
+ * allowance, and team management renders its seat limit instead.
154
+ */
155
+ const UNLIMITED_LIMIT_LABEL = 'Unlimited';
150
156
  function formatLimitedFeatureLabel(baseLabel, limitValue) {
151
157
  if (limitValue !== 1)
152
158
  return baseLabel;
@@ -185,6 +191,9 @@ function getFeatureDisplayParts(feature, seatLimit) {
185
191
  baseLabel: formatLimitedFeatureLabel(baseLabel, maxWorkspaces)
186
192
  };
187
193
  }
194
+ if (feature.access !== 'disabled') {
195
+ return { limitValue: UNLIMITED_LIMIT_LABEL, limitUnit: null, baseLabel };
196
+ }
188
197
  }
189
198
  if (feature.featureKey === 'collaboration.ai_profiles') {
190
199
  const maxAiProfiles = readConfiguredFeatureLimit(config, 'maxAiProfiles');
@@ -195,6 +204,9 @@ function getFeatureDisplayParts(feature, seatLimit) {
195
204
  baseLabel: formatLimitedFeatureLabel(baseLabel, maxAiProfiles)
196
205
  };
197
206
  }
207
+ if (feature.access !== 'disabled') {
208
+ return { limitValue: UNLIMITED_LIMIT_LABEL, limitUnit: null, baseLabel };
209
+ }
198
210
  }
199
211
  if (feature.featureKey === 'collaboration.team_management') {
200
212
  const normalizedSeatLimit = Number(seatLimit);
@@ -74,7 +74,7 @@ import { useRecentSyncEvents } from '../../hooks/sync/useRecentSyncEvents';
74
74
  import { useSyncStatusActions } from '../../hooks/sync/useSyncStatusActions';
75
75
  import { useSyncStatusControls } from '../../hooks/sync/useSyncStatusControls';
76
76
  import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
77
- import { AI_PROFILES_WORKSPACE_FEATURE_KEY, ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, PLANNING_WORKSPACE_FEATURE_KEY, TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, WORKFLOW_MANAGER_WORKSPACE_FEATURE_KEY, resolveFeatureAccess, } from '../../runtime/appGates';
77
+ import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccessMap, } from '../../runtime/appGates';
78
78
  // Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
79
79
  const TaskSettings = React.lazy(() => import('../features/TaskSettings').then(m => ({ default: m.TaskSettings })));
80
80
  const AnnotatedAttachmentWorkspaceShell = React.lazy(() => import('../features/AnnotatedAttachmentWorkspace').then(m => ({ default: m.AnnotatedAttachmentWorkspaceShell })));
@@ -171,7 +171,7 @@ export function StandaloneLayout(props) {
171
171
  // Data & Actions
172
172
  tasks, handleEdit, handleSubmit, resetForm, discardDescriptionImageDraft, handleUpdateTask, handleSetStatus: handleSetStatusDurably,
173
173
  // Form State (destructure all needed for TaskForm)
174
- editingTaskId, loading, error, title, setTitle, setTitleDraft, description, setDescription, setDescriptionDraft, checklistItems, setChecklistItems, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, status, setStatus, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, workstreamInput, setWorkstreamInput, manualComplexityEnabled, checklistDropdownEnabled, showTaskCardStatusLabel, formTaxonomies, setFormTaxonomies, comments, newCommentText, setNewCommentText, attachments, setAttachments, attachmentsDirty, setAttachmentsDirty, descriptionImageUploadPending, descriptionImageDraftId, registerDescriptionImageUpload, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp,
174
+ editingTaskId, loading, error, title, setTitle, setTitleDraft, description, setDescription, setDescriptionDraft, checklistItems, setChecklistItems, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, status, setStatus, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, workstreamInput, setWorkstreamInput, manualComplexityEnabled, checklistDropdownEnabled, showTaskCardStatusLabel, showTaskCardDescription, showTaskCardAttachments, showTaskCardChecklistProgress, showTaskCardLatestActivity, tintTaskCardActivityBackground, formTaxonomies, setFormTaxonomies, comments, newCommentText, setNewCommentText, attachments, setAttachments, attachmentsDirty, setAttachmentsDirty, descriptionImageUploadPending, descriptionImageDraftId, registerDescriptionImageUpload, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp,
175
175
  // Form Actions
176
176
  handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
177
177
  // Other Props for Form
@@ -401,32 +401,7 @@ export function StandaloneLayout(props) {
401
401
  setRequestedAnnotatedTarget(null);
402
402
  setRequestedAnnotatedSessionId(null);
403
403
  }, [layoutStateWorkspaceKey]);
404
- const workspaceFeatureAccess = useMemo(() => ({
405
- [DOCUMENT_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
406
- featureKey: DOCUMENT_WORKSPACE_FEATURE_KEY,
407
- runtimeMode,
408
- }),
409
- [ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
410
- featureKey: ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY,
411
- runtimeMode,
412
- }),
413
- [WORKFLOW_MANAGER_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
414
- featureKey: WORKFLOW_MANAGER_WORKSPACE_FEATURE_KEY,
415
- runtimeMode,
416
- }),
417
- [TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
418
- featureKey: TASKFORCE_AGENTS_WORKSPACE_FEATURE_KEY,
419
- runtimeMode,
420
- }),
421
- [AI_PROFILES_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
422
- featureKey: AI_PROFILES_WORKSPACE_FEATURE_KEY,
423
- runtimeMode,
424
- }),
425
- [PLANNING_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
426
- featureKey: PLANNING_WORKSPACE_FEATURE_KEY,
427
- runtimeMode,
428
- }),
429
- }), [runtimeMode]);
404
+ const workspaceFeatureAccess = useMemo(() => props.config?.appGates || resolveAppGateAccessMap('production'), [props.config?.appGates]);
430
405
  const workspaceModules = useMemo(() => getWorkspaceModuleDefinitions({ featureAccess: workspaceFeatureAccess }), [workspaceFeatureAccess]);
431
406
  const resolvedWorkspaceModule = useMemo(() => resolveWorkspaceModule(activeWorkspaceModule, workspaceModules), [activeWorkspaceModule, workspaceModules]);
432
407
  const planningSurfaceVisible = planningDrawerOpen || resolvedWorkspaceModule === 'planning';
@@ -1152,7 +1127,8 @@ export function StandaloneLayout(props) {
1152
1127
  prerequisiteTaskIds: prerequisiteTaskIdsByTaskId.get(task.id) || [],
1153
1128
  dependencyState: getDependencyState(task.id),
1154
1129
  assignee: task.assignee || null,
1155
- attachmentCount: Array.isArray(task.attachments) ? task.attachments.length : 0,
1130
+ attachmentCount: task.attachmentCount
1131
+ ?? (Array.isArray(task.attachments) ? task.attachments.length : 0),
1156
1132
  isArchived: Boolean(task.isArchived),
1157
1133
  });
1158
1134
  workstreamTaskPreviewsById.set(workstreamId, current);
@@ -4287,7 +4263,7 @@ export function StandaloneLayout(props) {
4287
4263
  const taskforceAgentDrawerNode = taskforceAgentDrawerOpen ? (_jsx("div", { className: `${styles.taskforceAgentDrawerOverlay} ${taskforceAgentPanelCollapsed ? styles.taskforceAgentDrawerOverlayCollapsed : ''}`.trim(), style: { top: `${taskforceAgentDrawerTop}px` }, "data-theme": currentTheme, "aria-label": "Taskforce Agent chat", children: _jsx("aside", { className: taskforceAgentPanelCollapsed
4288
4264
  ? `${shellStyles.workspaceToolRail} ${shellStyles.workspaceToolRailRight}`
4289
4265
  : styles.taskforceAgentDrawerPanel, children: taskforceAgentPanelCollapsed ? (_jsxs("div", { className: shellStyles.workspaceToolRailMain, children: [_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} tf-control-icon tf-control-icon-quiet tf-control-icon-active`, onClick: () => setTaskforceAgentPanelCollapsed(false), title: "Expand agent chat", "aria-label": "Expand agent chat", children: _jsx(Bot, { size: 16 }) }), _jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} tf-control-icon tf-control-icon-quiet`, onClick: handleCloseTaskforceAgentDrawer, title: "Close agent chat", "aria-label": "Close agent chat", children: _jsx(X, { size: 16 }) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.taskforceAgentDrawerHeader, children: [taskforceAgentDrawerHeaderContent || (_jsxs("div", { className: styles.taskforceAgentDrawerHeaderFallback, children: [_jsx("span", { className: styles.taskforceAgentDrawerFallbackAvatar, "aria-hidden": "true", children: _jsx(Bot, { size: 16 }) }), _jsx("strong", { children: "Loading agent..." })] })), _jsxs("div", { className: styles.taskforceAgentDrawerHeaderActions, children: [_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => setTaskforceAgentPanelCollapsed(true), title: "Collapse agent chat", "aria-label": "Collapse agent chat", children: _jsx(ChevronRight, { size: 16 }) }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: handleCloseTaskforceAgentDrawer, title: "Close agent chat", "aria-label": "Close agent chat", children: _jsx(X, { size: 16 }) })] })] }), _jsx(Suspense, { fallback: _jsx("div", { className: styles.taskforceAgentDrawerLoading, children: _jsx(Loader2, { size: 18, className: styles.spinner }) }), children: _jsx(TaskforceAgentsModule, { workspaceId: currentWorkspaceId, launchContext: taskforceAgentsLaunchContext, onClearLaunchContext: handleCloseTaskforceAgentDrawer, onOpenTaskContext: handleOpenTaskforceAgentTaskContext, taskReferences: taskReferences, surface: "taskDrawer", runtimeMode: runtimeMode, cloudAuthConfigured: cloudAuthConfigured, authSessionResolved: authSessionResolved, isAuthenticated: isAuthenticated, resolveCloudAuthUrl: resolveCloudAuthUrl, theme: currentTheme, rememberedAgentId: taskforceAgentId, rememberedConversationId: taskforceAgentConversationId, rememberedConversationByAgentId: taskforceAgentConversationByAgentId, rememberedSelectionReady: uiStateReady, onRememberSelection: handleRememberTaskforceAgentSelection, onRenderDrawerHeader: handleRenderTaskforceAgentDrawerHeader }) })] })) }) })) : null;
4290
- return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx("div", { ref: appShellHeaderRef, style: { flexShrink: 0 }, children: _jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, onBrandClick: handleOpenKanbanFromBrand, meta: (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: `${styles.taskCountBadge} ${styles.taskCountBadgeButton} ${taskHeaderQuickFilter === 'assigned-to-me' ? styles.taskCountBadgeActive : ''}`.trim(), onClick: () => handleTaskHeaderQuickFilter('assigned-to-me'), title: taskHeaderQuickFilter === 'assigned-to-me' ? 'Clear assigned-to-me quick filter' : 'Filter to my open tasks', "aria-label": `${taskHeaderQuickFilter === 'assigned-to-me' ? 'Clear' : 'Apply'} assigned to me quick filter (${assignedTaskCount} task${assignedTaskCount === 1 ? '' : 's'})`, "aria-pressed": taskHeaderQuickFilter === 'assigned-to-me', children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("button", { type: "button", className: `${styles.taskCountBadge} ${styles.taskCountBadgeButton} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''} ${taskHeaderQuickFilter === 'overdue' ? styles.taskCountBadgeActive : ''}`.trim(), onClick: () => handleTaskHeaderQuickFilter('overdue'), title: taskHeaderQuickFilter === 'overdue' ? 'Clear my-overdue-tasks quick filter' : 'Filter to my overdue tasks', "aria-label": `${taskHeaderQuickFilter === 'overdue' ? 'Clear' : 'Apply'} my overdue tasks quick filter (${assignedOverdueTaskCount} task${assignedOverdueTaskCount === 1 ? '' : 's'})`, "aria-pressed": taskHeaderQuickFilter === 'overdue', children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: openSyncStatusModal, title: `Sync manager: ${headerSyncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
4266
+ return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx("div", { ref: appShellHeaderRef, style: { flexShrink: 0 }, children: _jsx(AppShellHeader, { projectName: currentWorkspaceLabel, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, onBrandClick: handleOpenKanbanFromBrand, meta: (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: `${styles.taskCountBadge} ${styles.taskCountBadgeButton} ${taskHeaderQuickFilter === 'assigned-to-me' ? styles.taskCountBadgeActive : ''}`.trim(), onClick: () => handleTaskHeaderQuickFilter('assigned-to-me'), title: taskHeaderQuickFilter === 'assigned-to-me' ? 'Clear assigned-to-me quick filter' : 'Filter to my open tasks', "aria-label": `${taskHeaderQuickFilter === 'assigned-to-me' ? 'Clear' : 'Apply'} assigned to me quick filter (${assignedTaskCount} task${assignedTaskCount === 1 ? '' : 's'})`, "aria-pressed": taskHeaderQuickFilter === 'assigned-to-me', children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("button", { type: "button", className: `${styles.taskCountBadge} ${styles.taskCountBadgeButton} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''} ${taskHeaderQuickFilter === 'overdue' ? styles.taskCountBadgeActive : ''}`.trim(), onClick: () => handleTaskHeaderQuickFilter('overdue'), title: taskHeaderQuickFilter === 'overdue' ? 'Clear my-overdue-tasks quick filter' : 'Filter to my overdue tasks', "aria-label": `${taskHeaderQuickFilter === 'overdue' ? 'Clear' : 'Apply'} my overdue tasks quick filter (${assignedOverdueTaskCount} task${assignedOverdueTaskCount === 1 ? '' : 's'})`, "aria-pressed": taskHeaderQuickFilter === 'overdue', children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: openSyncStatusModal, title: `Sync manager: ${headerSyncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
4291
4267
  marginLeft: '6px',
4292
4268
  height: '24px',
4293
4269
  width: '24px',
@@ -4358,7 +4334,7 @@ export function StandaloneLayout(props) {
4358
4334
  if (count > 0 && window.confirm(`Permanently delete all ${count} tasks in Trash? Filters and selection do not limit this action. This cannot be undone.`)) {
4359
4335
  void handleEmptyDeletedTasks();
4360
4336
  }
4361
- } })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, filtersReady: hasInitedFilters, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: scheduleFilteredTaskIdList, copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, workspaceId: currentWorkspaceId, deletedTaskRecordByTaskId: deletedTaskRecordByTaskId, selectedDeletedRecordIds: trashSelection.selectedIds, onDeletedSelectionChange: trashSelection.toggle, onUpdateTask: handleUpdateTask, onTaskClick: handleEdit, taskReferences: taskTextReferences, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: handleBoardScheduleDaySelected, persistedScrollLeft: groupBy === 'schedule' ? scheduleScrollLeft : undefined, onScrollLeftChange: handleBoardScrollLeftChange, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, readOnlyMode: taskScope === 'deleted' ? 'deleted' : taskScope === 'archived' ? 'archived' : null, recentlyChangedTaskIds: props.recentlyChangedTaskIds, sortBy: sortBy, sortOrder: props.sortOrder, planningDropTargets: planningDrawerPortalElement
4337
+ } })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, filtersReady: hasInitedFilters, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: scheduleFilteredTaskIdList, copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, workspaceId: currentWorkspaceId, deletedTaskRecordByTaskId: deletedTaskRecordByTaskId, selectedDeletedRecordIds: trashSelection.selectedIds, onDeletedSelectionChange: trashSelection.toggle, onUpdateTask: handleUpdateTask, onTaskClick: handleEdit, taskReferences: taskTextReferences, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, showTaskCardDescription: runtimeMode !== 'cloud' && showTaskCardDescription, showTaskCardAttachments: showTaskCardAttachments, showTaskCardChecklistProgress: showTaskCardChecklistProgress, showTaskCardLatestActivity: showTaskCardLatestActivity, tintTaskCardActivityBackground: tintTaskCardActivityBackground, onScheduleDaySelected: handleBoardScheduleDaySelected, persistedScrollLeft: groupBy === 'schedule' ? scheduleScrollLeft : undefined, onScrollLeftChange: handleBoardScrollLeftChange, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, readOnlyMode: taskScope === 'deleted' ? 'deleted' : taskScope === 'archived' ? 'archived' : null, recentlyChangedTaskIds: props.recentlyChangedTaskIds, sortBy: sortBy, sortOrder: props.sortOrder, planningDropTargets: planningDrawerPortalElement
4362
4338
  ? createPortal(planningDrawerNode, planningDrawerPortalElement)
4363
4339
  : null, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAssignWorkstreamToInitiative: handleAssignInitiativeToWorkstream, workstreams: props.workstreams, initiatives: props.initiatives, onUnarchive: handleBoardUnarchive, onDelete: handleBoardDelete }, `${groupBy}-${kanbanColumns.map(c => String(c.value)).join('|')}-${scheduleDates.mon}`), groupBy === 'schedule' && !scheduleSidebarOpen && (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
4364
4340
  setPlanningDrawerDetail(null);
@@ -42,7 +42,7 @@ export function WidgetView(props) {
42
42
  // Actions
43
43
  handleEdit, handleDelete, handleCopyId, handleUpdateTask, handleToggleComplete, handleToggleCancel, handleToggleInProgress, handleToggleReview, handleSetStatus, handleArchiveTask, handleBulkArchive, handleUnarchive, handleRestoreDeletedTask, handleRestoreSelectedDeletedTasks, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, fetchArchive,
44
44
  // Form State
45
- editingTaskId, loading, error, clearTaskError, title, setTitle, setTitleDraft, description, setDescription, setDescriptionDraft, checklistItems, setChecklistItems, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, status, setStatus, manualComplexityEnabled, checklistDropdownEnabled, showTaskCardStatusLabel, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, workstreamInput, setWorkstreamInput, formTaxonomies, setFormTaxonomies, comments, newCommentText, setNewCommentText, attachments, setAttachments, setAttachmentsDirty, descriptionImageUploadPending, descriptionImageDraftId, registerDescriptionImageUpload, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp,
45
+ editingTaskId, loading, error, clearTaskError, title, setTitle, setTitleDraft, description, setDescription, setDescriptionDraft, checklistItems, setChecklistItems, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, status, setStatus, manualComplexityEnabled, checklistDropdownEnabled, showTaskCardStatusLabel, showTaskCardDescription, showTaskCardAttachments, showTaskCardChecklistProgress, showTaskCardLatestActivity, tintTaskCardActivityBackground, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, workstreamInput, setWorkstreamInput, formTaxonomies, setFormTaxonomies, comments, newCommentText, setNewCommentText, attachments, setAttachments, setAttachmentsDirty, descriptionImageUploadPending, descriptionImageDraftId, registerDescriptionImageUpload, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp,
46
46
  // Form Actions
47
47
  handleSubmit, resetForm, discardDescriptionImageDraft, handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
48
48
  // Modals
@@ -54,7 +54,7 @@ export function WidgetView(props) {
54
54
  // Refs
55
55
  commentsEndRef,
56
56
  // Props from Core
57
- currentWorkspaceId, authUserId, settingsModel, onHeaderMouseDown, isDragging } = props;
57
+ currentWorkspaceId, authUserId, runtimeMode, settingsModel, onHeaderMouseDown, isDragging } = props;
58
58
  const taskReferences = useTaskReferenceContract(tasks, handleOpenTaskById);
59
59
  const handleCanonicalEntityReference = useCallback((reference) => {
60
60
  if (reference.kind === 'initiative' || reference.kind === 'workstream') {
@@ -346,7 +346,7 @@ export function WidgetView(props) {
346
346
  if (confirm(`Permanently delete all ${deletedTasks.length} tasks in Trash? Filters and selection do not limit this action. This cannot be undone.`)) {
347
347
  void handleEmptyDeletedTasks();
348
348
  }
349
- } })), copiedId: copiedId, recentlyChangedTaskIds: recentlyChangedTaskIds, showTaskCardStatusLabel: showTaskCardStatusLabel, workstreams: props.workstreams, initiatives: props.initiatives, onSearchChange: setSearchQuery, onFilterCategoriesChange: (vals) => setFilterCategories(vals), onFilterTypesChange: (vals) => setFilterTypes(vals), onFilterPrioritiesChange: setFilterPriorities, onFilterStatusChange: setFilterStatus, onFilterAssigneesChange: (vals) => setFilterAssignees(vals), onTaxonomyFilterChange: (id, values) => setFilterTaxonomies(prev => ({ ...prev, [id]: values })), onSortByChange: handleSortByChange, onSortOrderChange: toggleSortOrder, onShowArchiveChange: setShowArchive, onTaskScopeChange: setTaskScope, onClearFilters: clearFilters, onToggleCategory: (category) => setCollapsedCategories(prev => ({ ...prev, [category]: !prev[category] })), onEditTask: handleEdit, onUpdateTask: handleUpdateTask, taskReferences: taskTextReferences, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onBulkArchive: handleBulkArchive, onUnarchive: (taskId) => {
349
+ } })), copiedId: copiedId, recentlyChangedTaskIds: recentlyChangedTaskIds, showTaskCardStatusLabel: showTaskCardStatusLabel, showTaskCardDescription: runtimeMode !== 'cloud' && showTaskCardDescription, showTaskCardAttachments: showTaskCardAttachments, showTaskCardChecklistProgress: showTaskCardChecklistProgress, showTaskCardLatestActivity: showTaskCardLatestActivity, tintTaskCardActivityBackground: tintTaskCardActivityBackground, workstreams: props.workstreams, initiatives: props.initiatives, onSearchChange: setSearchQuery, onFilterCategoriesChange: (vals) => setFilterCategories(vals), onFilterTypesChange: (vals) => setFilterTypes(vals), onFilterPrioritiesChange: setFilterPriorities, onFilterStatusChange: setFilterStatus, onFilterAssigneesChange: (vals) => setFilterAssignees(vals), onTaxonomyFilterChange: (id, values) => setFilterTaxonomies(prev => ({ ...prev, [id]: values })), onSortByChange: handleSortByChange, onSortOrderChange: toggleSortOrder, onShowArchiveChange: setShowArchive, onTaskScopeChange: setTaskScope, onClearFilters: clearFilters, onToggleCategory: (category) => setCollapsedCategories(prev => ({ ...prev, [category]: !prev[category] })), onEditTask: handleEdit, onUpdateTask: handleUpdateTask, taskReferences: taskTextReferences, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onBulkArchive: handleBulkArchive, onUnarchive: (taskId) => {
350
350
  const deletedRecord = deletedRecordByTaskId.get(taskId);
351
351
  if (deletedRecord) {
352
352
  void handleRestoreDeletedTask(deletedRecord.id, deletedRecord.taskId);
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React from 'react';
3
3
  import { useDndContext, useDraggable, useDroppable } from '@dnd-kit/core';
4
4
  import { CSS } from '@dnd-kit/utilities';
5
- import { ArrowUpDown, Archive, ChevronDown, ChevronLeft, ChevronRight, Eye, EyeOff, Filter, GripVertical, Kanban, Link2, Pencil, Plus, RotateCcw, Search, Star, Unlink, X, } from 'lucide-react';
5
+ import { ArrowUpDown, Archive, ChevronDown, ChevronLeft, ChevronRight, Eye, EyeOff, Filter, GripVertical, Kanban, Pencil, Plus, RotateCcw, Search, Star, Unlink, X, } from 'lucide-react';
6
6
  import styles from '../../../Taskforce.module.css';
7
7
  import panelStyles from './PlanningDrawer.module.css';
8
8
  import { HierarchyActionBadge } from '../../ui/HierarchyActionBadge';
@@ -500,7 +500,7 @@ function PlanningDrawerList({ initiatives, standaloneWorkstreams, planningLoadSt
500
500
  : expandedInitiativeIds.has(initiative.id);
501
501
  return (_jsxs("li", { children: [_jsx(PlanningRow, { rowId: initiative.id, rowType: "initiative", referenceLabel: formatInitiativeReference(initiative), item: initiative, ownerOption: initiative.ownerId ? assigneeOptionByValue.get(initiative.ownerId) : undefined, ownerOptions: assigneeOptions, onChangeOwner: (ownerId) => onChangePlanningOwner('initiative', initiative.id, ownerId === 'unassigned' ? '' : ownerId), onChangeIdentity: (patch) => onChangePlanningIdentity('initiative', initiative.id, patch), workstreamCount: treeWorkstreams.length, active: activeInitiativeId === initiative.id && !activeWorkstreamId, isArchived: Boolean(initiative.isArchived), detailOpen: detail?.type === 'initiative' && detail.item.id === initiative.id, canExpand: allWorkstreams.length > 0, expanded: expanded, controlsId: `planning-children-${initiative.id}`, onToggleExpand: () => onToggleInitiative(initiative.id), onToggleScope: () => onSelectInitiative(initiative.id), onOpenDetails: () => onOpenInitiativeDetails(initiative.id), favorite: planningFavoriteKeys.has(getPlanningFavoriteKey({ type: 'initiative', id: initiative.id })), favoriteReady: navigatorSortReady, onToggleFavorite: () => onTogglePlanningFavorite({ type: 'initiative', id: initiative.id }), onArchiveReady: onArchiveResolvedInitiative
502
502
  ? () => onArchiveResolvedInitiative(initiative.id)
503
- : undefined }), expanded && displayedWorkstreams.length > 0 && (_jsx("ul", { id: `planning-children-${initiative.id}`, className: panelStyles.treeChildren, "aria-label": `Workstreams in ${initiative.title}`, children: displayedWorkstreams.map((workstream) => (_jsxs("li", { children: [_jsx(PlanningRow, { rowId: workstream.id, rowType: "workstream", referenceLabel: formatWorkstreamReference(workstream), item: workstream, ownerOption: workstream.ownerId ? assigneeOptionByValue.get(workstream.ownerId) : undefined, ownerOptions: assigneeOptions, onChangeOwner: (ownerId) => onChangePlanningOwner('workstream', workstream.id, ownerId === 'unassigned' ? '' : ownerId), onChangeIdentity: (patch) => onChangePlanningIdentity('workstream', workstream.id, patch), active: activeWorkstreamId === workstream.id, isArchived: Boolean(workstream.isArchived), detailOpen: (detail?.type === 'workstream' && detail.item.id === workstream.id)
503
+ : undefined }), expanded && displayedWorkstreams.length > 0 && (_jsx("ul", { id: `planning-children-${initiative.id}`, className: `${panelStyles.treeChildren} ${panelStyles.treeWorkstreamChildren}`.trim(), "aria-label": `Workstreams in ${initiative.title}`, children: displayedWorkstreams.map((workstream) => (_jsxs("li", { children: [_jsx(PlanningRow, { rowId: workstream.id, rowType: "workstream", referenceLabel: formatWorkstreamReference(workstream), item: workstream, ownerOption: workstream.ownerId ? assigneeOptionByValue.get(workstream.ownerId) : undefined, ownerOptions: assigneeOptions, onChangeOwner: (ownerId) => onChangePlanningOwner('workstream', workstream.id, ownerId === 'unassigned' ? '' : ownerId), onChangeIdentity: (patch) => onChangePlanningIdentity('workstream', workstream.id, patch), active: activeWorkstreamId === workstream.id, isArchived: Boolean(workstream.isArchived), detailOpen: (detail?.type === 'workstream' && detail.item.id === workstream.id)
504
504
  || (secondaryPane?.kind === 'detail' && secondaryPane.detail.item.id === workstream.id), canExpand: (workstream.tasks || []).length > 0, expanded: expandedWorkstreamIds.has(workstream.id), controlsId: `planning-tasks-${workstream.id}`, onToggleExpand: () => toggleWorkstreamTasks(workstream.id), onToggleScope: () => onSelectWorkstream(workstream.id, initiative.id), onOpenDetails: () => {
505
505
  onOpenInitiativeDetails(initiative.id);
506
506
  onOpenNestedWorkstreamDetails(workstream.id);
@@ -521,7 +521,6 @@ function PlanningFailureState({ title, message, retrying, onRetry, compact = fal
521
521
  function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor, selectedChildWorkstreamId, currentWorkspaceId, currentActorId, theme, onOpenNestedWorkstreamDetails, onArchivePlanningTask, onArchiveResolvedWorkstream, onArchiveResolvedEntity, onEdit, onChangeOwner, onChangePlanningOwner, onChangePlanningIdentity, onArchive, onUnarchive, onCreateTaskInWorkstream, onCreateWorkstreamInInitiative, onOpenTaskById, onChangeTaskAssignee, taskReferences, onAssignTaskToWorkstream, onAssignInitiativeToWorkstream, onAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile, onRemovePlanningContextFile, onUpdatePlanningContextCaption, parentInitiative = null, assigneeOptions, linkOptions, showTaskCardStatusLabel = false, taskArrangeMode = 'execution', onTaskArrangeModeChange = () => { }, onReorderWorkstreamTasks, onSetTaskStatus, hydrationState, onRetryHydration, }) {
522
522
  const inlineEditorFormId = React.useId();
523
523
  const [isAttachingReference, setIsAttachingReference] = React.useState(false);
524
- const [showParentLinkPicker, setShowParentLinkPicker] = React.useState(false);
525
524
  const [pendingUnlink, setPendingUnlink] = React.useState(null);
526
525
  const [relationshipRecovery, setRelationshipRecovery] = React.useState(null);
527
526
  const [isChangingRelationship, setIsChangingRelationship] = React.useState(false);
@@ -590,7 +589,6 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
590
589
  : `Archive ${detailLabel.toLowerCase()}`;
591
590
  React.useEffect(() => {
592
591
  setIsAttachingReference(false);
593
- setShowParentLinkPicker(false);
594
592
  setPendingUnlink(null);
595
593
  setRelationshipRecovery(null);
596
594
  setIsChangingRelationship(false);
@@ -658,9 +656,7 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
658
656
  return;
659
657
  setIsChangingRelationship(true);
660
658
  try {
661
- const result = await onAssignInitiativeToWorkstream(detail.item.id, option.referenceLabel);
662
- if (result !== false)
663
- setShowParentLinkPicker(false);
659
+ await onAssignInitiativeToWorkstream(detail.item.id, option.referenceLabel);
664
660
  }
665
661
  finally {
666
662
  setIsChangingRelationship(false);
@@ -762,7 +758,7 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
762
758
  });
763
759
  }, title: "Unlink workstream from initiative", ariaLabel: "Unlink workstream from initiative", children: _jsx(Unlink, { size: 14, "aria-hidden": "true" }) })) : null] })) : null })) : null, detail.type === 'workstream'
764
760
  && !parentInitiativeReferenceLabel
765
- && !inlineEditor ? (_jsx(HierarchyActionBadge, { disabled: !onAssignInitiativeToWorkstream, onClick: () => setShowParentLinkPicker(true), title: "Link initiative", ariaLabel: "Link initiative", ariaExpanded: showParentLinkPicker, children: _jsx(Link2, { size: 14, "aria-hidden": "true" }) })) : null, _jsx(PlanningReferenceBadge, { label: detailReferenceLabel || 'Reference pending', entityType: detail.type, entityName: detail.item.title, pending: !detailReferenceLabel })] }), _jsx("div", { className: panelStyles.detailTopActions, children: inlineEditor ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: inlineEditor.onCancel, disabled: inlineEditor.isSubmitting, children: "Cancel" }), _jsx("button", { type: "submit", form: inlineEditorFormId, className: "tf-button-primary tf-button-compact", disabled: inlineEditor.isSubmitting, children: inlineEditor.isSubmitting ? 'Saving…' : 'Save' })] })) : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-control-icon", disabled: archiveDetailState.mode === 'blocked' || archiveDetailState.mode === 'checking', onClick: () => {
761
+ && !inlineEditor ? (_jsx(PlanningLinkPicker, { options: linkOptions.initiatives, entityLabel: "initiative", onSelect: handleLinkInitiative, disabled: !onAssignInitiativeToWorkstream || isChangingRelationship, iconTrigger: true, triggerTitle: "Link initiative" })) : null, _jsx(PlanningReferenceBadge, { label: detailReferenceLabel || 'Reference pending', entityType: detail.type, entityName: detail.item.title, pending: !detailReferenceLabel })] }), _jsx("div", { className: panelStyles.detailTopActions, children: inlineEditor ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: inlineEditor.onCancel, disabled: inlineEditor.isSubmitting, children: "Cancel" }), _jsx("button", { type: "submit", form: inlineEditorFormId, className: "tf-button-primary tf-button-compact", disabled: inlineEditor.isSubmitting, children: inlineEditor.isSubmitting ? 'Saving…' : 'Save' })] })) : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-control-icon", disabled: archiveDetailState.mode === 'blocked' || archiveDetailState.mode === 'checking', onClick: () => {
766
762
  if (archiveDetailState.mode === 'archived') {
767
763
  onUnarchive?.();
768
764
  }
@@ -776,10 +772,7 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
776
772
  ? archiveEntityConfirmationVisible
777
773
  : undefined, children: archiveDetailState.mode === 'archived'
778
774
  ? _jsx(RotateCcw, { size: 14, "aria-hidden": "true" })
779
- : _jsx(Archive, { size: 14, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
780
- setShowParentLinkPicker(false);
781
- onEdit();
782
- }, title: `Edit ${detailLabel.toLowerCase()} details`, "aria-label": `Edit ${detailLabel.toLowerCase()} details`, children: _jsx(Pencil, { size: 14, "aria-hidden": "true" }) })] })) })] }), archiveEntityConfirmationVisible && archiveDetailState.mode === 'ready' ? (_jsx(PlanningArchiveConfirmation, { entityType: detail.type, title: detail.item.title, activeTaskCount: detail.item.archiveActiveTaskCount, activeWorkstreamCount: detail.item.archiveActiveWorkstreamCount, busy: archiveEntityBusy, onCancel: () => setArchiveEntityConfirmationVisible(false), onConfirm: () => void handleConfirmArchiveEntity() })) : null, detail.type === 'workstream' ? renderRelationshipFeedback('workstream', detail.item.id) : null, detail.type === 'workstream' && !parentInitiativeReferenceLabel && showParentLinkPicker ? (_jsxs("div", { className: panelStyles.relationshipPickerRow, role: "group", "aria-label": "Link workstream to initiative", children: [_jsx(PlanningLinkPicker, { options: linkOptions.initiatives, entityLabel: "initiative", onSelect: handleLinkInitiative, disabled: !onAssignInitiativeToWorkstream || isChangingRelationship }), _jsx("button", { type: "button", className: "tf-control-icon tf-control-icon-compact", disabled: isChangingRelationship, onClick: () => setShowParentLinkPicker(false), title: "Cancel initiative link", "aria-label": "Cancel initiative link", children: _jsx(X, { size: 14, "aria-hidden": "true" }) })] })) : null, inlineEditor ? (_jsx(PlanningDrawerEditor, { editor: inlineEditor.editor, draftTitle: inlineEditor.draftTitle, draftDescription: inlineEditor.draftDescription, draftOwner: inlineEditor.draftOwner, draftInitiativeId: inlineEditor.draftInitiativeId, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: inlineEditor.draftInitiativeSummary, onChangeDraftTitle: inlineEditor.onChangeDraftTitle, onChangeDraftDescription: inlineEditor.onChangeDraftDescription, onChangeDraftOwner: inlineEditor.onChangeDraftOwner, onChangeDraftInitiativeId: inlineEditor.onChangeDraftInitiativeId, onCancel: inlineEditor.onCancel, onSubmit: inlineEditor.onSubmit, isSubmitting: inlineEditor.isSubmitting, inline: true, formId: inlineEditorFormId })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: panelStyles.detailTitleRow, children: [_jsx(PlanningVisualIdentity, { entityType: detail.type, entityId: detail.item.id, icon: detail.item.icon, color: detail.item.color, size: "detail", disabled: Boolean(detail.item.isArchived), onChange: onChangePlanningIdentity
775
+ : _jsx(Archive, { size: 14, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: onEdit, title: `Edit ${detailLabel.toLowerCase()} details`, "aria-label": `Edit ${detailLabel.toLowerCase()} details`, children: _jsx(Pencil, { size: 14, "aria-hidden": "true" }) })] })) })] }), archiveEntityConfirmationVisible && archiveDetailState.mode === 'ready' ? (_jsx(PlanningArchiveConfirmation, { entityType: detail.type, title: detail.item.title, activeTaskCount: detail.item.archiveActiveTaskCount, activeWorkstreamCount: detail.item.archiveActiveWorkstreamCount, busy: archiveEntityBusy, onCancel: () => setArchiveEntityConfirmationVisible(false), onConfirm: () => void handleConfirmArchiveEntity() })) : null, detail.type === 'workstream' ? renderRelationshipFeedback('workstream', detail.item.id) : null, inlineEditor ? (_jsx(PlanningDrawerEditor, { editor: inlineEditor.editor, draftTitle: inlineEditor.draftTitle, draftDescription: inlineEditor.draftDescription, draftOwner: inlineEditor.draftOwner, draftInitiativeId: inlineEditor.draftInitiativeId, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: inlineEditor.draftInitiativeSummary, onChangeDraftTitle: inlineEditor.onChangeDraftTitle, onChangeDraftDescription: inlineEditor.onChangeDraftDescription, onChangeDraftOwner: inlineEditor.onChangeDraftOwner, onChangeDraftInitiativeId: inlineEditor.onChangeDraftInitiativeId, onCancel: inlineEditor.onCancel, onSubmit: inlineEditor.onSubmit, isSubmitting: inlineEditor.isSubmitting, inline: true, formId: inlineEditorFormId })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: panelStyles.detailTitleRow, children: [_jsx(PlanningVisualIdentity, { entityType: detail.type, entityId: detail.item.id, icon: detail.item.icon, color: detail.item.color, size: "detail", disabled: Boolean(detail.item.isArchived), onChange: onChangePlanningIdentity
783
776
  ? (patch) => onChangePlanningIdentity(detail.type, detail.item.id, patch)
784
777
  : undefined }), _jsx("h3", { className: panelStyles.detailTitle, children: detail.item.title })] }), _jsx(PlanningEntityDescription, { description: detail.item.description, label: detailLabel, taskReferences: taskReferences, className: `${panelStyles.detailDescription} ${styles.appScrollbar} tf-scrollbar`.trim(), markdownClassName: panelStyles.detailDescriptionMarkdown })] })), _jsxs("div", { className: panelStyles.detailOwnerField, children: [_jsx("span", { className: panelStyles.detailOwnerLabel, children: "Owner" }), _jsx(PlanningOwnerDropdown, { value: ownerValue, disabled: !onChangeOwner || isSavingOwner, onChange: (nextOwnerId) => void handleOwnerChange(nextOwnerId), ariaLabel: `${detailLabel} owner`, options: assigneeOptions, className: `${planningDropdownStyles.control} ${panelStyles.ownerSelect}` })] }), detail.item.taskCount > 0 ? (_jsx(PlanningProgress, { value: detail.item.progressPercent, label: `${detail.item.progressPercent}% resolved`, statusCounts: detail.item.statusCounts })) : (_jsx(PlanningNoTasks, {})), _jsx(PlanningSummaryMeta, { item: detail.item, workstreamCount: detail.type === 'initiative'
785
778
  ? detail.item.workstreamCount ?? detail.item.workstreams.length
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
3
  import { CreditCard, KeyRound, Link2, RefreshCw, ShieldCheck, User } from 'lucide-react';
4
4
  import { Modal } from '../../../ui/Modal';
5
5
  import { EditableAvatarButton } from '../../../ui/EditableAvatarButton';
@@ -9,6 +9,7 @@ import styles from '../../../../Taskforce.module.css';
9
9
  import settingsStyles from './AccountSettingsModal.module.css';
10
10
  import { resolveCommercialLifecyclePresentation } from '../../../../utils/commercialLifecyclePresentation';
11
11
  import { getPasswordPolicyGuidance, validatePasswordPolicy } from '../../../../shared/passwordPolicy';
12
+ import { PasswordInput } from '../../../ui/PasswordInput';
12
13
  const PROVIDER_LABELS = {
13
14
  password: 'Email & Password',
14
15
  google: 'Google',
@@ -53,11 +54,7 @@ export function AccountSettingsModal({ isOpen, theme, displayName, email, avatar
53
54
  const linkedProviders = new Set(loginMethods.map((method) => method.provider));
54
55
  const hasPassword = linkedProviders.has('password');
55
56
  const linkableProviders = availableAuthProviders.filter((provider) => provider !== 'password' && !linkedProviders.has(provider));
56
- useEffect(() => {
57
- if (!isOpen)
58
- return;
59
- setActiveSection('profile');
60
- setUnlinkError(null);
57
+ const clearCredentialState = useCallback(() => {
61
58
  setShowAddPassword(false);
62
59
  setAddPasswordValue('');
63
60
  setAddPasswordError(null);
@@ -68,16 +65,28 @@ export function AccountSettingsModal({ isOpen, theme, displayName, email, avatar
68
65
  setConfirmPasswordValue('');
69
66
  setChangePasswordError(null);
70
67
  setChangePasswordDone(false);
71
- }, [isOpen]);
68
+ }, []);
69
+ const handleClose = () => {
70
+ clearCredentialState();
71
+ onClose();
72
+ };
73
+ const handleSectionChange = (section) => {
74
+ if (section !== 'security')
75
+ clearCredentialState();
76
+ setActiveSection(section);
77
+ };
78
+ useEffect(() => {
79
+ setUnlinkError(null);
80
+ clearCredentialState();
81
+ if (!isOpen)
82
+ return;
83
+ setActiveSection('profile');
84
+ }, [clearCredentialState, isOpen]);
72
85
  useEffect(() => {
73
86
  if (activeSection === 'security')
74
87
  return;
75
- setShowChangePassword(false);
76
- setCurrentPasswordValue('');
77
- setNewPasswordValue('');
78
- setConfirmPasswordValue('');
79
- setChangePasswordError(null);
80
- }, [activeSection]);
88
+ clearCredentialState();
89
+ }, [activeSection, clearCredentialState]);
81
90
  useEffect(() => {
82
91
  if (!isOpen || activeSection !== 'security' || !cloudAuthEnabled)
83
92
  return;
@@ -173,7 +182,7 @@ export function AccountSettingsModal({ isOpen, theme, displayName, email, avatar
173
182
  setShowChangePassword(true);
174
183
  setChangePasswordDone(false);
175
184
  setChangePasswordError(null);
176
- }, children: "Change password" }) })), showChangePassword && (_jsxs("div", { className: settingsStyles.addPasswordForm, children: [_jsxs("label", { className: settingsStyles.fieldLabel, children: [_jsx("span", { children: "Current password" }), _jsx("input", { type: "password", className: styles.input, value: currentPasswordValue, onChange: (event) => setCurrentPasswordValue(event.target.value), disabled: changePasswordBusy, autoComplete: "current-password" })] }), _jsxs("label", { className: settingsStyles.fieldLabel, children: [_jsx("span", { children: "New password" }), _jsx("input", { type: "password", className: styles.input, value: newPasswordValue, onChange: (event) => setNewPasswordValue(event.target.value), disabled: changePasswordBusy, autoComplete: "new-password" })] }), _jsxs("label", { className: settingsStyles.fieldLabel, children: [_jsx("span", { children: "Confirm new password" }), _jsx("input", { type: "password", className: styles.input, value: confirmPasswordValue, onChange: (event) => setConfirmPasswordValue(event.target.value), disabled: changePasswordBusy, autoComplete: "new-password" })] }), _jsx("p", { className: "tf-text-helper", children: addPasswordGuidance.join(' ') }), changePasswordError && _jsx("p", { className: "tf-text-error", role: "alert", children: changePasswordError }), _jsxs("div", { className: settingsStyles.inlineActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: () => {
185
+ }, children: "Change password" }) })), showChangePassword && (_jsxs("div", { className: settingsStyles.addPasswordForm, children: [_jsxs("div", { className: settingsStyles.fieldLabel, children: [_jsx("label", { htmlFor: "account-current-password", children: "Current password" }), _jsx(PasswordInput, { id: "account-current-password", className: styles.input, value: currentPasswordValue, onChange: (event) => setCurrentPasswordValue(event.target.value), disabled: changePasswordBusy, autoComplete: "current-password", visibilityLabel: "current password" })] }), _jsxs("div", { className: settingsStyles.fieldLabel, children: [_jsx("label", { htmlFor: "account-new-password", children: "New password" }), _jsx(PasswordInput, { id: "account-new-password", className: styles.input, value: newPasswordValue, onChange: (event) => setNewPasswordValue(event.target.value), disabled: changePasswordBusy, autoComplete: "new-password", visibilityLabel: "new password" })] }), _jsxs("div", { className: settingsStyles.fieldLabel, children: [_jsx("label", { htmlFor: "account-confirm-password", children: "Confirm new password" }), _jsx(PasswordInput, { id: "account-confirm-password", className: styles.input, value: confirmPasswordValue, onChange: (event) => setConfirmPasswordValue(event.target.value), disabled: changePasswordBusy, autoComplete: "new-password", visibilityLabel: "confirm new password" })] }), _jsx("p", { className: "tf-text-helper", children: addPasswordGuidance.join(' ') }), changePasswordError && _jsx("p", { className: "tf-text-error", role: "alert", children: changePasswordError }), _jsxs("div", { className: settingsStyles.inlineActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: () => {
177
186
  setShowChangePassword(false);
178
187
  setCurrentPasswordValue('');
179
188
  setNewPasswordValue('');
@@ -183,15 +192,15 @@ export function AccountSettingsModal({ isOpen, theme, displayName, email, avatar
183
192
  setShowAddPassword(true);
184
193
  setAddPasswordDone(false);
185
194
  setAddPasswordError(null);
186
- }, children: "Add password login" }) })), showAddPassword && (_jsxs("div", { className: settingsStyles.addPasswordForm, children: [_jsxs("label", { className: settingsStyles.fieldLabel, children: [_jsx("span", { children: "New password" }), _jsx("input", { type: "password", className: styles.input, placeholder: "Use 12+ characters", value: addPasswordValue, onChange: (event) => setAddPasswordValue(event.target.value), disabled: addPasswordBusy, autoComplete: "new-password" })] }), _jsx("p", { className: "tf-text-helper", children: addPasswordGuidance.join(' ') }), addPasswordError && _jsx("p", { className: "tf-text-error", role: "alert", children: addPasswordError }), _jsxs("div", { className: settingsStyles.inlineActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: () => {
195
+ }, children: "Add password login" }) })), showAddPassword && (_jsxs("div", { className: settingsStyles.addPasswordForm, children: [_jsxs("div", { className: settingsStyles.fieldLabel, children: [_jsx("label", { htmlFor: "account-add-password", children: "New password" }), _jsx(PasswordInput, { id: "account-add-password", className: styles.input, placeholder: "Use 12+ characters", value: addPasswordValue, onChange: (event) => setAddPasswordValue(event.target.value), disabled: addPasswordBusy, autoComplete: "new-password", visibilityLabel: "new password" })] }), _jsx("p", { className: "tf-text-helper", children: addPasswordGuidance.join(' ') }), addPasswordError && _jsx("p", { className: "tf-text-error", role: "alert", children: addPasswordError }), _jsxs("div", { className: settingsStyles.inlineActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: () => {
187
196
  setShowAddPassword(false);
188
197
  setAddPasswordValue('');
189
198
  setAddPasswordError(null);
190
199
  }, disabled: addPasswordBusy, children: "Cancel" }), _jsx("button", { type: "button", className: "tf-button-primary tf-button-compact", onClick: () => { void handleAddPassword(); }, disabled: addPasswordBusy || !addPasswordValue, children: addPasswordBusy ? 'Saving…' : 'Save password' })] })] })), !loginMethodsLoading && !loginMethodsError && linkableProviders.length > 0 && (_jsxs("div", { className: settingsStyles.linkProviderBlock, children: [_jsx("p", { className: "tf-label-micro", children: "Link another account" }), _jsx("div", { className: settingsStyles.inlineActions, children: linkableProviders.map((provider) => (_jsxs("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: () => onLinkProvider(provider), children: [_jsx(Link2, { size: 15 }), PROVIDER_LABELS[provider] || provider] }, provider))) })] }))] })] }));
191
200
  const renderBillingSection = () => (_jsxs("section", { className: settingsStyles.section, "aria-labelledby": "account-billing-heading", children: [_jsxs("div", { className: settingsStyles.sectionHeader, children: [_jsxs("div", { children: [_jsx("h2", { className: "tf-heading-card", id: "account-billing-heading", children: "Plan & Billing" }), _jsx("p", { className: "tf-text-secondary", children: "Review your plan and open the secure billing portal." })] }), _jsxs("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: onRefreshBilling, disabled: billingActionBusy || billingLoading, children: [_jsx(RefreshCw, { size: 14 }), "Refresh"] })] }), _jsxs("div", { className: `${settingsStyles.sectionCard} tf-surface-panel`, children: [billingLoading && _jsx("p", { className: "tf-text-secondary", children: "Loading billing status\u2026" }), billingError && _jsx("p", { className: "tf-text-error", role: "alert", children: billingError }), billingActionError && _jsx("p", { className: "tf-text-error", role: "alert", children: billingActionError }), billingNotice && _jsx("p", { className: "tf-text-secondary", role: "status", children: billingNotice }), _jsxs("div", { className: settingsStyles.planSummary, children: [_jsxs("div", { children: [_jsx("p", { className: "tf-label-micro", children: "Current plan" }), _jsx("strong", { className: settingsStyles.planName, children: resolvedPlanLabel })] }), _jsx("span", { className: `tf-chip ${lifecyclePresentation.isActive ? 'tf-chip-success' : 'tf-chip-neutral'}`, children: formatStatusLabel(accountProfileSummary?.entitlementState) })] }), lifecyclePresentation.message && (_jsx("p", { className: lifecyclePresentation.statusTone === 'error' ? 'tf-text-error' : 'tf-text-secondary', children: lifecyclePresentation.message })), accountProfileSummary?.stripeSubscriptionId && (_jsxs("div", { className: settingsStyles.intervalRow, children: [_jsxs("label", { className: settingsStyles.fieldLabel, children: [_jsx("span", { children: "Billing interval" }), _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => onBillingIntervalChange(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }), _jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: onUpdateInterval, disabled: billingActionBusy, children: "Update interval" })] })), _jsxs("div", { className: settingsStyles.sectionActions, children: [(lifecyclePresentation.primaryAction === 'manage_billing' || lifecyclePresentation.primaryAction === 'none') && (_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: onOpenPlans, disabled: billingActionBusy, children: "View plans" })), lifecyclePresentation.showManageBilling && lifecyclePresentation.primaryAction !== 'manage_billing' && (_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: onManageBilling, disabled: billingActionBusy, children: "Manage billing" })), lifecyclePresentation.primaryLabel && (_jsx("button", { type: "button", className: "tf-button-primary tf-button-compact", onClick: handlePrimaryBillingAction, disabled: billingActionBusy, children: billingActionBusy ? 'Working…' : lifecyclePresentation.primaryLabel }))] })] })] }));
192
- return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Account Settings", size: "mdWide", theme: theme, draggable: true, children: _jsx("div", { className: `${modalStyles.form} ${settingsStyles.modalBody}`, children: _jsxs("div", { className: settingsStyles.layout, children: [_jsx("div", { className: settingsStyles.sectionTabs, role: "tablist", "aria-label": "Account settings sections", "aria-orientation": "vertical", children: SECTION_META.map((section) => {
201
+ return (_jsx(Modal, { isOpen: isOpen, onClose: handleClose, title: "Account Settings", size: "mdWide", theme: theme, draggable: true, children: _jsx("div", { className: `${modalStyles.form} ${settingsStyles.modalBody}`, children: _jsxs("div", { className: settingsStyles.layout, children: [_jsx("div", { className: settingsStyles.sectionTabs, role: "tablist", "aria-label": "Account settings sections", "aria-orientation": "vertical", children: SECTION_META.map((section) => {
193
202
  const Icon = section.icon;
194
203
  const selected = section.id === activeSection;
195
- return (_jsxs("button", { type: "button", role: "tab", id: `account-settings-tab-${section.id}`, "aria-controls": `account-settings-panel-${section.id}`, "aria-selected": selected, className: settingsStyles.sectionTab, onClick: () => setActiveSection(section.id), children: [_jsx(Icon, { size: 16 }), _jsx("span", { children: section.label })] }, section.id));
204
+ return (_jsxs("button", { type: "button", role: "tab", id: `account-settings-tab-${section.id}`, "aria-controls": `account-settings-panel-${section.id}`, "aria-selected": selected, className: settingsStyles.sectionTab, onClick: () => handleSectionChange(section.id), children: [_jsx(Icon, { size: 16 }), _jsx("span", { children: section.label })] }, section.id));
196
205
  }) }), _jsxs("div", { className: settingsStyles.sectionPanel, role: "tabpanel", id: `account-settings-panel-${activeSection}`, "aria-labelledby": `account-settings-tab-${activeSection}`, children: [activeSection === 'profile' && renderProfileSection(), activeSection === 'security' && renderSecuritySection(), activeSection === 'billing' && renderBillingSection()] })] }) }) }));
197
206
  }
@@ -143,10 +143,15 @@ const SPECS = [
143
143
  { key: 'TASKFORCE_BEDROCK_SMOKE_MAX_TOKENS', description: 'Maximum output tokens for live Bedrock smoke tests.', category: 'AI', defaultValue: '128', targets: ['server', 'all'] },
144
144
  { key: 'TASKFORCE_BEDROCK_MANTLE_MODEL_KEY', description: 'Optional model key for Bedrock Mantle smoke tests (openai.gpt-5-4|xai.grok-4-3).', category: 'AI', defaultValue: 'openai.gpt-5-4', targets: ['server', 'all'] },
145
145
  { key: 'TASKFORCE_BEDROCK_ENV_FILE', description: 'Optional env file path for the Bedrock smoke script.', category: 'AI', defaultValue: '.env.local', targets: ['server', 'all'] },
146
+ { key: 'TASKFORCE_AGENT_SUBSCRIPTION_DEV_MODELS', description: 'Enable internal local-development-only Taskforce Agent subscription-auth model adapters such as OpenAI Codex app-server.', category: 'AI', defaultValue: 'false', targets: ['server', 'all'] },
147
+ { key: 'TASKFORCE_CODEX_APP_SERVER_COMMAND', description: 'Optional Codex CLI command path for the internal OpenAI Codex app-server Taskforce Agent adapter.', category: 'AI', defaultValue: 'codex', targets: ['server', 'all'] },
148
+ { key: 'TASKFORCE_CODEX_APP_SERVER_CWD', description: 'Optional working directory for the internal Codex app-server subprocess. Defaults to the system temp directory.', category: 'AI', targets: ['server', 'all'] },
146
149
  { key: 'TASKFORCE_ALLOWED_ORIGINS', description: 'Allowed CORS origins (comma-separated).', category: 'Security', targets: ['server', 'all'] },
147
150
  { key: 'TASKFORCE_API_RATE_LIMIT_ENABLED', description: 'Enable API rate limiter.', category: 'Security', defaultValue: 'true', targets: ['server', 'all'] },
148
151
  { key: 'TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS', description: 'API rate limit max requests per window.', category: 'Security', defaultValue: '120', targets: ['server', 'all'] },
149
152
  { key: 'TASKFORCE_API_RATE_LIMIT_WINDOW_MS', description: 'API rate limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
153
+ { key: 'TASKFORCE_SYNC_FEED_RATE_LIMIT_MAX_REQUESTS', description: 'Workspace sync feed read limit per window.', category: 'Security', defaultValue: '600', targets: ['server', 'all'] },
154
+ { key: 'TASKFORCE_SYNC_FEED_RATE_LIMIT_WINDOW_MS', description: 'Workspace sync feed read limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
150
155
  { key: 'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS', description: 'Stripe webhook-specific rate limit max requests per window.', category: 'Security', defaultValue: '600', targets: ['server', 'all'] },
151
156
  { key: 'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS', description: 'Stripe webhook-specific rate limit window size in ms.', category: 'Security', defaultValue: '60000', targets: ['server', 'all'] },
152
157
  { key: 'TASKFORCE_MAX_PAYLOAD_BYTES', description: 'Maximum accepted API payload size in bytes.', category: 'Security', defaultValue: '1048576', targets: ['server', 'all'] },
@@ -168,6 +173,8 @@ const SPECS = [
168
173
  { key: 'TASKFORCE_BUILD_DEPLOY_ID', description: 'Build deploy ID metadata.', category: 'Build', targets: ['server', 'all'] },
169
174
  { key: 'TASKFORCE_UI_DIST_DIR', description: 'Override the standalone UI distribution directory for isolated local QA runtimes.', category: 'Build', targets: ['server', 'all'] },
170
175
  { key: 'TASKFORCE_REQUIRE_EXACT_PORT', description: 'Require the standalone server to use only its requested port.', category: 'Core', defaultValue: 'false', targets: ['server', 'all'] },
176
+ { key: 'TASKFORCE_QA_EXPECTED_WORKSPACE_ID', description: 'Require isolated cloud QA to resolve the exact expected workspace ID before mutation.', category: 'Core', targets: ['server', 'all'] },
177
+ { key: 'TASKFORCE_QA_EXPECTED_WORKSPACE_SLUG', description: 'Require isolated cloud QA to resolve the expected workspace slug before mutation.', category: 'Core', targets: ['server', 'all'] },
171
178
  { key: 'DEBUG_MODE', description: 'Expose debug-only client diagnostics such as pricing source metadata.', category: 'Build', defaultValue: 'false', targets: ['all'] },
172
179
  { key: 'VITE_TASKFORCE_BASE_URL', description: 'Client base URL for both API and cloud auth when specific overrides are not set.', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
173
180
  { key: 'VITE_TASKFORCE_API_BASE_URL', description: 'Client API base URL (overrides VITE_TASKFORCE_BASE_URL when set).', category: 'Client', example: 'https://performance-app.taskforcehq.ai', targets: ['all'] },
@@ -185,17 +192,20 @@ const SPECS = [
185
192
  { key: 'SYNC_SOAK_PROFILE', description: 'Soak profile (smoke|stress|overnight).', category: 'Soak', defaultValue: 'smoke', targets: ['soak', 'all'] },
186
193
  { key: 'SYNC_SOAK_DURATION_MIN', description: 'Soak duration in minutes.', category: 'Soak', defaultValue: '60', targets: ['soak', 'all'] },
187
194
  { key: 'SYNC_SOAK_SEED', description: 'Deterministic soak seed.', category: 'Soak', example: '4242', targets: ['soak', 'all'] },
195
+ { key: 'SYNC_SOAK_TASK_ID_NAMESPACE', description: 'Optional per-run namespace for deterministic generated soak task IDs.', category: 'Soak', targets: ['soak', 'all'] },
188
196
  { key: 'SYNC_SOAK_ARTIFACT_DIR', description: 'Soak artifacts output directory.', category: 'Soak', defaultValue: '.taskforce/soak-artifacts', targets: ['soak', 'all'] },
189
197
  { key: 'SYNC_SOAK_ENABLE_SYNC_DEBUG', description: 'Enable sync debug diagnostics during soak.', category: 'Soak', defaultValue: 'false', targets: ['soak', 'all'] },
190
198
  { key: 'SYNC_SOAK_STRESS_ACTIONS', description: 'Stress action count.', category: 'Soak', defaultValue: '150', targets: ['soak', 'all'] },
191
199
  { key: 'SYNC_SOAK_STRESS_CHECKPOINT_INTERVAL', description: 'Stress checkpoint interval.', category: 'Soak', defaultValue: '50', targets: ['soak', 'all'] },
192
200
  { key: 'SYNC_SOAK_STRESS_SETTLE_RETRIES', description: 'Stress settle retry count.', category: 'Soak', defaultValue: '3', targets: ['soak', 'all'] },
193
201
  { key: 'SYNC_SOAK_STRESS_SETTLE_WAIT_MS', description: 'Stress settle wait time in ms.', category: 'Soak', defaultValue: '500', targets: ['soak', 'all'] },
202
+ { key: 'SYNC_SOAK_STRESS_BOOTSTRAP_PUSH_SETTLE', description: 'Enable legacy bootstrap-push checkpoint settlement in stress and overnight profiles.', category: 'Soak', defaultValue: 'true', targets: ['soak', 'all'] },
194
203
  { key: 'SYNC_SOAK_STRESS_HARD_RESET_SETTLE', description: 'Enable hard reset settle path in stress.', category: 'Soak', defaultValue: 'true', targets: ['soak', 'all'] },
195
204
  { key: 'SYNC_SOAK_OVERNIGHT_ACTION_INTERVAL_MS', description: 'Overnight action pacing interval in ms.', category: 'Soak', defaultValue: '60000', targets: ['soak', 'all'] },
196
205
  { key: 'SYNC_SOAK_OVERNIGHT_CHECKPOINT_MIN', description: 'Overnight checkpoint interval in minutes.', category: 'Soak', defaultValue: '15', targets: ['soak', 'all'] },
197
206
  { key: 'SYNC_SOAK_OVERNIGHT_MAX_ACTIONS', description: 'Overnight hard cap on action count.', category: 'Soak', defaultValue: '10000', targets: ['soak', 'all'] },
198
207
  { key: 'SYNC_SOAK_OVERNIGHT_MIN_ACTIONS', description: 'Overnight minimum actions expected before pass.', category: 'Soak', defaultValue: '80', targets: ['soak', 'all'] },
208
+ { key: 'SYNC_SOAK_OVERNIGHT_CLEAN_BASELINE_EVERY_ACTION', description: 'Require coordinator settlement before each accelerated overnight action.', category: 'Soak', defaultValue: 'false', targets: ['soak', 'all'] },
199
209
  { key: 'CF_ACCESS_CLIENT_ID', description: 'Cloudflare Access service token client id (soak).', category: 'Cloudflare', secret: true, targets: ['soak', 'all'] },
200
210
  { key: 'CF_ACCESS_CLIENT_SECRET', description: 'Cloudflare Access service token client secret (soak).', category: 'Cloudflare', secret: true, targets: ['soak', 'all'] }
201
211
  ];
@@ -255,6 +265,8 @@ function validateCommonNumbers(env, errors) {
255
265
  'PORT',
256
266
  'TASKFORCE_API_RATE_LIMIT_MAX_REQUESTS',
257
267
  'TASKFORCE_API_RATE_LIMIT_WINDOW_MS',
268
+ 'TASKFORCE_SYNC_FEED_RATE_LIMIT_MAX_REQUESTS',
269
+ 'TASKFORCE_SYNC_FEED_RATE_LIMIT_WINDOW_MS',
258
270
  'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_MAX_REQUESTS',
259
271
  'TASKFORCE_STRIPE_WEBHOOK_RATE_LIMIT_WINDOW_MS',
260
272
  'TASKFORCE_MAX_PAYLOAD_BYTES',