@taskforcehq/taskforce 0.3.303 → 0.3.305

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +102 -5
  3. package/dist/TaskforceCore.js +635 -238
  4. package/dist/TaskforceCore.test.js +2243 -495
  5. package/dist/annotatedAttachments/service.d.ts +1 -1
  6. package/dist/annotatedAttachments/types.d.ts +1 -1
  7. package/dist/compat/workspaceSyncCompat.js +6 -0
  8. package/dist/components/features/AgentsModule.js +1 -1
  9. package/dist/components/features/AgentsModule.test.js +1 -1
  10. package/dist/components/features/DocumentIndex.d.ts +1 -1
  11. package/dist/components/features/DocumentIndex.js +1 -1
  12. package/dist/components/features/DocumentViewer.d.ts +1 -1
  13. package/dist/components/features/DocumentViewer.js +32 -9
  14. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  15. package/dist/components/features/DocumentWorkspace.js +12 -155
  16. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  17. package/dist/components/features/TaskSettings.js +87 -41
  18. package/dist/components/features/TaskSettings.test.js +180 -40
  19. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  20. package/dist/components/features/documentWorkspaceModel.js +57 -0
  21. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  22. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  23. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  24. package/dist/components/task/TaskActionHeader.js +3 -3
  25. package/dist/components/task/TaskActionHeader.test.js +8 -0
  26. package/dist/components/task/TaskCard.js +16 -1
  27. package/dist/components/task/TaskCard.test.js +31 -0
  28. package/dist/components/task/TaskForm.d.ts +9 -8
  29. package/dist/components/task/TaskForm.js +374 -219
  30. package/dist/components/task/TaskForm.test.js +482 -361
  31. package/dist/components/task/TaskKanban.d.ts +1 -1
  32. package/dist/components/task/TaskKanban.js +33 -161
  33. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  34. package/dist/components/task/TaskStatusActions.js +8 -3
  35. package/dist/components/task/TaskStatusActions.test.js +9 -0
  36. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  37. package/dist/components/task/taskKanbanDropRules.js +187 -0
  38. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  39. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  40. package/dist/components/ui/Modal.d.ts +1 -1
  41. package/dist/components/ui/Modal.js +1 -0
  42. package/dist/components/views/AppShellHeader.js +5 -1
  43. package/dist/components/views/AppShellHeader.test.js +4 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  45. package/dist/components/views/PlanComparisonPage.js +9 -10
  46. package/dist/components/views/PlanComparisonPage.test.js +55 -41
  47. package/dist/components/views/PlansPage.d.ts +5 -1
  48. package/dist/components/views/PlansPage.js +150 -81
  49. package/dist/components/views/PlansPage.test.js +367 -41
  50. package/dist/components/views/StandaloneLayout.js +135 -54
  51. package/dist/components/views/WidgetView.js +11 -2
  52. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  53. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  54. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  55. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  56. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  57. package/dist/config/envSchema.js +1 -1
  58. package/dist/core/AiProfileService.d.ts +6 -1
  59. package/dist/core/AiProfileService.js +161 -16
  60. package/dist/core/AiProfileService.test.js +3 -1
  61. package/dist/core/AiProfiles.test.js +200 -0
  62. package/dist/core/AuthTokenService.test.d.ts +1 -0
  63. package/dist/core/AuthTokenService.test.js +78 -0
  64. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  65. package/dist/core/EntitlementsPolicy.test.js +75 -13
  66. package/dist/core/PlanEntitlementService.d.ts +9 -1
  67. package/dist/core/PlanEntitlementService.js +144 -19
  68. package/dist/core/PlanVersionPolicy.test.js +19 -26
  69. package/dist/core/SignupMonetizationSettings.test.js +75 -15
  70. package/dist/core/SystemAdmin.test.js +212 -56
  71. package/dist/core/TaskActivityService.d.ts +4 -1
  72. package/dist/core/TaskActivityService.js +45 -0
  73. package/dist/core/TaskAuditTimeline.test.js +52 -1
  74. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  75. package/dist/core/Taskforce.d.ts +18 -11
  76. package/dist/core/Taskforce.js +93 -12
  77. package/dist/core/WorkspacePlanMode.test.js +73 -8
  78. package/dist/core/shared.d.ts +2 -0
  79. package/dist/core/shared.js +11 -0
  80. package/dist/documentReviews/service.d.ts +1 -1
  81. package/dist/documentReviews/types.d.ts +1 -1
  82. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  83. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  84. package/dist/hooks/sync/auth.d.ts +37 -0
  85. package/dist/hooks/sync/auth.js +63 -0
  86. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  87. package/dist/hooks/sync/bootstrap.js +52 -0
  88. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  89. package/dist/hooks/sync/orchestratorShared.js +209 -0
  90. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  91. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  92. package/dist/hooks/sync/realtime.d.ts +25 -0
  93. package/dist/hooks/sync/realtime.js +60 -0
  94. package/dist/hooks/sync/recovery.d.ts +69 -0
  95. package/dist/hooks/sync/recovery.js +118 -0
  96. package/dist/hooks/sync/transfers.d.ts +123 -0
  97. package/dist/hooks/sync/transfers.js +435 -0
  98. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  99. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  100. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  101. package/dist/hooks/ui/useResourceExport.js +5 -2
  102. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  103. package/dist/hooks/useRealtimeSync.js +2 -1
  104. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  105. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  106. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  107. package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
  108. package/dist/hooks/useSyncOrchestrator.js +388 -879
  109. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
  110. package/dist/hooks/useTaskMutations.d.ts +2 -1
  111. package/dist/hooks/useTaskMutations.js +57 -7
  112. package/dist/hooks/useTaskMutations.test.js +176 -0
  113. package/dist/hooks/useTaskforce.d.ts +13 -4
  114. package/dist/hooks/useTaskforce.js +302 -817
  115. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  116. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  117. package/dist/hooks/useWorkspaceSyncController.js +33 -11
  118. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  119. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  120. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  121. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  122. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  123. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  124. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  125. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  126. package/dist/localization/locales/en-US.js +2 -2
  127. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  128. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  129. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  130. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  131. package/dist/mcp/clientIntegrations.d.ts +12 -1
  132. package/dist/mcp/clientIntegrations.js +161 -6
  133. package/dist/mcp/clientIntegrations.test.js +76 -4
  134. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  135. package/dist/mcp/collaborationRegistrar.js +71 -0
  136. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  137. package/dist/mcp/documentAssetRegistrar.js +346 -0
  138. package/dist/mcp/documentHelpers.d.ts +100 -0
  139. package/dist/mcp/documentHelpers.js +161 -0
  140. package/dist/mcp/runtime.js +2455 -3925
  141. package/dist/mcp/runtime.test.js +240 -8
  142. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  143. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  144. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  145. package/dist/mcp/taskAttachmentTypes.js +1 -0
  146. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  147. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  148. package/dist/mcp/toolCatalog.d.ts +35 -0
  149. package/dist/mcp/toolCatalog.js +3 -0
  150. package/dist/plugins/vite.js +1 -1
  151. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  152. package/dist/runtime/appGateDefinitions.js +1 -1
  153. package/dist/server/index.d.ts +1 -0
  154. package/dist/server/index.js +46 -3
  155. package/dist/server/index.test.js +12 -1
  156. package/dist/server/routes/admin.d.ts +1 -5
  157. package/dist/server/routes/admin.js +151 -80
  158. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  159. package/dist/server/routes/annotatedAttachments.js +1 -1
  160. package/dist/server/routes/auth.d.ts +1 -4
  161. package/dist/server/routes/auth.js +59 -64
  162. package/dist/server/routes/authSupport.d.ts +30 -0
  163. package/dist/server/routes/authSupport.js +81 -0
  164. package/dist/server/routes/billing.d.ts +1 -1
  165. package/dist/server/routes/billing.js +122 -255
  166. package/dist/server/routes/billing.test.js +257 -86
  167. package/dist/server/routes/documentReviews.d.ts +1 -1
  168. package/dist/server/routes/documentReviews.js +1 -1
  169. package/dist/server/routes/documents.d.ts +4 -2
  170. package/dist/server/routes/documents.js +73 -7
  171. package/dist/server/routes/primitives.d.ts +11 -0
  172. package/dist/server/routes/primitives.js +73 -0
  173. package/dist/server/routes/resources.d.ts +1 -1
  174. package/dist/server/routes/resources.js +1 -1
  175. package/dist/server/routes/shared.d.ts +31 -1
  176. package/dist/server/routes/shared.js +26 -0
  177. package/dist/server/routes/sync.d.ts +1 -1
  178. package/dist/server/routes/sync.integration.test.js +20 -0
  179. package/dist/server/routes/sync.js +111 -238
  180. package/dist/server/routes/tasks.d.ts +1 -1
  181. package/dist/server/routes/tasks.js +50 -8
  182. package/dist/server/routes/workspaces.d.ts +1 -1
  183. package/dist/server/routes/workspaces.js +1 -1
  184. package/dist/server/routes.d.ts +2 -9
  185. package/dist/server/routes.js +84 -181
  186. package/dist/server/routes.test.js +912 -32
  187. package/dist/shared/taskActor.d.ts +10 -0
  188. package/dist/shared/taskActor.js +1 -0
  189. package/dist/sync/collaborationSyncPayload.js +4 -0
  190. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  191. package/dist/sync/collaborationSyncState.d.ts +3 -1
  192. package/dist/sync/collaborationSyncState.js +19 -0
  193. package/dist/sync/contentSyncState.js +28 -17
  194. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  195. package/dist/sync/syncApplyHandlers.js +370 -8
  196. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  197. package/dist/sync/syncApplyHandlers.test.js +379 -0
  198. package/dist/sync/syncService.d.ts +9 -1
  199. package/dist/sync/syncService.js +44 -2
  200. package/dist/sync/syncService.test.js +62 -0
  201. package/dist/sync/workspacePullFeed.d.ts +1 -0
  202. package/dist/sync/workspacePullFeed.js +66 -59
  203. package/dist/sync/workspacePullFeed.test.js +72 -4
  204. package/dist/sync/workspaceSyncModel.d.ts +25 -1
  205. package/dist/sync/workspaceSyncModel.js +77 -3
  206. package/dist/sync/workspaceSyncModel.test.js +153 -0
  207. package/dist/sync/workspaceSyncState.d.ts +2 -0
  208. package/dist/sync/workspaceSyncState.js +1 -0
  209. package/dist/types.d.ts +2 -10
  210. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
  211. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  212. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  213. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  214. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  215. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  216. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  217. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  218. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
  219. package/dist/ui/assets/index-C2-OXZV7.css +1 -0
  220. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  221. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
  222. package/dist/ui/index.html +4 -4
  223. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  224. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  225. package/dist/utils/taskActivity.js +4 -0
  226. package/dist/utils/taskActivity.test.js +25 -1
  227. package/package.json +10 -1
  228. package/scripts/check-cloud-mcp.mjs +83 -0
  229. package/scripts/playwright-auth.sh +9 -1
  230. package/scripts/run-billing-performance-smoke.sh +24 -0
  231. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  232. package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
  233. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  234. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  235. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  236. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  237. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -1,42 +1,44 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';
4
- import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
5
- import { CSS } from '@dnd-kit/utilities';
6
- import * as Icons from 'lucide-react';
7
- import { HelpCircle, X, Send, Check, ChevronDown, ChevronRight, User, ClipboardList, Plus, Bot, GripVertical } from 'lucide-react';
8
- import styles from '../../Taskforce.module.css';
9
- import formStyles from './TaskForm.module.css';
10
- import { DEFAULT_CATEGORY_LABEL, DEFAULT_CATEGORY_VALUE } from '../../shared/taxonomyDefaults.js';
11
- import { LevelSelector } from '../ui/LevelSelector';
12
- import { TaxonomyDropdown } from '../ui/TaxonomyDropdown';
13
- import { Markdown } from '../ui/Markdown';
14
- import { IconMap, resolveColor } from '../../utils/constants';
15
- import { formatDate } from '../../utils/formatting';
16
- import { t } from '../../localization';
17
- import { TaskContextUpload } from './TaskContextUpload';
18
- import { summarizeTaskChange, summarizeTaskEvent } from '../../utils/taskActivity';
19
- import { ensureAssigneeOptionPresent, getAssigneeKind, getAssigneeLabel, } from '../../utils/assignees';
20
- import { getTaskReferenceLabel } from '../../utils/taskReferences';
21
- import { getVisibleTaskFormTaxonomies } from '../../utils/taxonomyFormDefaults.js';
22
- import { getWorkstreamReferenceLabel } from '../../utils/planningReferences';
23
- function SortableChecklistRow({ id, item, onToggle, onRemove }) {
2
+ import React from "react";
3
+ import { DndContext, PointerSensor, closestCenter, useSensor, useSensors, } from "@dnd-kit/core";
4
+ import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable";
5
+ import { CSS } from "@dnd-kit/utilities";
6
+ import * as Icons from "lucide-react";
7
+ import { HelpCircle, X, Send, Check, ChevronDown, ChevronRight, User, ClipboardList, Plus, Bot, GripVertical, } from "lucide-react";
8
+ import styles from "../../Taskforce.module.css";
9
+ import formStyles from "./TaskForm.module.css";
10
+ import { DEFAULT_CATEGORY_LABEL, DEFAULT_CATEGORY_VALUE, } from "../../shared/taxonomyDefaults.js";
11
+ import { LevelSelector } from "../ui/LevelSelector";
12
+ import { TaxonomyDropdown } from "../ui/TaxonomyDropdown";
13
+ import { Markdown } from "../ui/Markdown";
14
+ import { IconMap, resolveColor } from "../../utils/constants";
15
+ import { formatDate } from "../../utils/formatting";
16
+ import { t } from "../../localization";
17
+ import { TaskContextUpload } from "./TaskContextUpload";
18
+ import { summarizeTaskChange, summarizeTaskEvent, } from "../../utils/taskActivity";
19
+ import { ensureAssigneeOptionPresent, getAssigneeKind, getAssigneeLabel, } from "../../utils/assignees";
20
+ import { getTaskReferenceLabel } from "../../utils/taskReferences";
21
+ import { getVisibleTaskFormTaxonomies } from "../../utils/taxonomyFormDefaults.js";
22
+ import { getInitiativeReferenceLabel, getWorkstreamReferenceLabel, } from "../../utils/planningReferences";
23
+ function SortableChecklistRow({ id, item, onToggle, onRemove, }) {
24
24
  const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id });
25
25
  const style = {
26
26
  transform: CSS.Transform.toString(transform),
27
27
  transition,
28
28
  opacity: isDragging ? 0.88 : 1,
29
29
  };
30
- return (_jsxs("div", { ref: setNodeRef, style: style, className: `${formStyles.checklistRow} ${isDragging ? formStyles.checklistRowDragging : ''}`.trim(), children: [_jsx("button", { type: "button", className: formStyles.checklistHandleBtn, title: "Reorder checklist item", "aria-label": "Reorder checklist item", ...attributes, ...listeners, children: _jsx(GripVertical, { size: 14 }) }), _jsx("button", { type: "button", className: `${formStyles.checklistCheckboxBtn} ${item.isCompleted ? formStyles.checklistCheckboxBtnChecked : ''}`.trim(), onClick: onToggle, title: item.isCompleted ? 'Mark incomplete' : 'Mark complete', "aria-label": item.isCompleted ? 'Mark checklist item incomplete' : 'Mark checklist item complete', children: item.isCompleted ? _jsx(Check, { size: 14, strokeWidth: 2.1 }) : null }), _jsx("span", { className: `${formStyles.checklistItemText} ${item.isCompleted ? formStyles.checklistItemTextCompleted : ''}`.trim(), children: item.title }), _jsx("button", { type: "button", className: formStyles.checklistRemoveBtn, onClick: onRemove, title: "Remove checklist item", "aria-label": "Remove checklist item", children: _jsx(X, { size: 15, strokeWidth: 1.9 }) })] }));
30
+ return (_jsxs("div", { ref: setNodeRef, style: style, className: `${formStyles.checklistRow} ${isDragging ? formStyles.checklistRowDragging : ""}`.trim(), children: [_jsx("button", { type: "button", className: formStyles.checklistHandleBtn, title: "Reorder checklist item", "aria-label": "Reorder checklist item", ...attributes, ...listeners, children: _jsx(GripVertical, { size: 14 }) }), _jsx("button", { type: "button", className: `${formStyles.checklistCheckboxBtn} ${item.isCompleted ? formStyles.checklistCheckboxBtnChecked : ""}`.trim(), onClick: onToggle, title: item.isCompleted ? "Mark incomplete" : "Mark complete", "aria-label": item.isCompleted
31
+ ? "Mark checklist item incomplete"
32
+ : "Mark checklist item complete", children: item.isCompleted ? _jsx(Check, { size: 14, strokeWidth: 2.1 }) : null }), _jsx("span", { className: `${formStyles.checklistItemText} ${item.isCompleted ? formStyles.checklistItemTextCompleted : ""}`.trim(), children: item.title }), _jsx("button", { type: "button", className: formStyles.checklistRemoveBtn, onClick: onRemove, title: "Remove checklist item", "aria-label": "Remove checklist item", children: _jsx(X, { size: 15, strokeWidth: 1.9 }) })] }));
31
33
  }
32
34
  function hexToRgba(hex, opacity) {
33
35
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
34
36
  if (!result)
35
- return '';
37
+ return "";
36
38
  return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${opacity})`;
37
39
  }
38
40
  export function TaskForm(props) {
39
- const { editingTaskId, error, title, description, category, type, priority, complexity, manualComplexityEnabled = false, assignee, scheduledDate, dueDate, workstreamInput = '', checklistItems, comments, newCommentText, contextFiles, currentWorkspaceId, apiBaseUrl, descriptionFocused, showMarkdownHelp, showChecklist = false, checklistEnabled = true, showComments, formTaxonomies, onTaxonomyChange, onOpenSettings, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions, taxonomies, workstreams = [], copiedId, onTitleChange, onDescriptionChange, onCategoryChange, onTypeChange, onPriorityChange, onComplexityChange, onAssigneeChange, onScheduledDateChange, onDueDateChange, onWorkstreamInputChange = () => { }, onChecklistItemsChange, onNewCommentTextChange, onDescriptionFocusedChange, onShowMarkdownHelpChange, onShowChecklistChange = () => { }, onShowCommentsChange, onSubmit, onAddComment, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onOpenTaskById, currentTask, commentsEndRef, } = props;
41
+ const { editingTaskId, error, title, description, category, type, priority, complexity, manualComplexityEnabled = false, assignee, scheduledDate, dueDate, workstreamInput = "", checklistItems, comments, newCommentText, contextFiles, currentWorkspaceId, apiBaseUrl, descriptionFocused, showMarkdownHelp, showChecklist = false, checklistEnabled = true, formTaxonomies, onTaxonomyChange, onOpenSettings, categories, types, priorities, taxonomyDisplayLabels, assigneeOptions, taxonomies, workstreams = [], initiatives = [], copiedId, onTitleChange, onDescriptionChange, onCategoryChange, onTypeChange, onPriorityChange, onComplexityChange, onAssigneeChange, onScheduledDateChange, onDueDateChange, onWorkstreamInputChange = () => { }, onChecklistItemsChange, onNewCommentTextChange, onDescriptionFocusedChange, onShowMarkdownHelpChange, onShowChecklistChange = () => { }, onSubmit, onAddComment, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onOpenTaskById, currentTask, commentsEndRef, } = props;
40
42
  const currentTaskReferenceLabel = getTaskReferenceLabel(currentTask);
41
43
  const visibleTaxonomies = React.useMemo(() => getVisibleTaskFormTaxonomies(taxonomies, formTaxonomies), [taxonomies, formTaxonomies]);
42
44
  const categoryOptions = React.useMemo(() => {
@@ -44,40 +46,40 @@ export function TaskForm(props) {
44
46
  const defaultOption = {
45
47
  value: DEFAULT_CATEGORY_VALUE,
46
48
  label: configuredDefault?.label || DEFAULT_CATEGORY_LABEL,
47
- icon: configuredDefault?.icon || 'HelpCircle',
48
- color: configuredDefault?.color || 'var(--text-secondary)'
49
+ icon: configuredDefault?.icon || "HelpCircle",
50
+ color: configuredDefault?.color || "var(--text-secondary)",
49
51
  };
50
52
  const selectedCategory = categories.find((cat) => cat.value === category);
51
53
  const availableCategories = categories.filter((cat) => !cat.disabled || cat.value === selectedCategory?.value);
52
54
  const nonDefault = availableCategories
53
- .filter(cat => cat.value !== DEFAULT_CATEGORY_VALUE)
54
- .map(cat => ({
55
+ .filter((cat) => cat.value !== DEFAULT_CATEGORY_VALUE)
56
+ .map((cat) => ({
55
57
  value: cat.value,
56
58
  label: cat.disabled ? `${cat.label} (Legacy)` : cat.label,
57
59
  icon: cat.icon,
58
- color: cat.color
60
+ color: cat.color,
59
61
  }));
60
62
  return [defaultOption, ...nonDefault];
61
63
  }, [categories, category]);
62
64
  const typeOptions = React.useMemo(() => {
63
65
  const selectedType = types.find((entry) => entry.value === type);
64
- const availableTypes = types.filter((entry) => entry.status !== 'retired' || entry.value === selectedType?.value);
66
+ const availableTypes = types.filter((entry) => entry.status !== "retired" || entry.value === selectedType?.value);
65
67
  return availableTypes.map((entry) => ({
66
68
  value: entry.value,
67
- label: entry.status === 'retired' ? `${entry.label} (Retired)` : entry.label,
68
- icon: entry.icon || 'Box',
69
- color: entry.color || 'violet-500'
69
+ label: entry.status === "retired" ? `${entry.label} (Retired)` : entry.label,
70
+ icon: entry.icon || "Box",
71
+ color: entry.color || "violet-500",
70
72
  }));
71
73
  }, [types, type]);
72
74
  const formatMetaDate = (value) => {
73
75
  if (!value)
74
76
  return null;
75
77
  const formatted = formatDate(value, {
76
- month: 'short',
77
- day: 'numeric',
78
- year: 'numeric',
79
- hour: '2-digit',
80
- minute: '2-digit'
78
+ month: "short",
79
+ day: "numeric",
80
+ year: "numeric",
81
+ hour: "2-digit",
82
+ minute: "2-digit",
81
83
  });
82
84
  return formatted || null;
83
85
  };
@@ -87,29 +89,32 @@ export function TaskForm(props) {
87
89
  const todayDateOnly = React.useMemo(() => {
88
90
  const now = new Date();
89
91
  const y = now.getFullYear();
90
- const m = String(now.getMonth() + 1).padStart(2, '0');
91
- const d = String(now.getDate()).padStart(2, '0');
92
+ const m = String(now.getMonth() + 1).padStart(2, "0");
93
+ const d = String(now.getDate()).padStart(2, "0");
92
94
  return `${y}-${m}-${d}`;
93
95
  }, []);
94
96
  const dueBeforeScheduledWarning = Boolean(dueDate && scheduledDate && dueDate < scheduledDate);
95
97
  const overdueWarning = Boolean(dueDate &&
96
98
  dueDate < todayDateOnly &&
97
- (!currentTask || (currentTask.status !== 'done' && currentTask.status !== 'cancelled')));
98
- const completedChecklistCount = checklistItems.filter(item => item.isCompleted).length;
99
- const [newChecklistItemText, setNewChecklistItemText] = React.useState('');
99
+ (!currentTask ||
100
+ (currentTask.status !== "done" && currentTask.status !== "cancelled")));
101
+ const completedChecklistCount = checklistItems.filter((item) => item.isCompleted).length;
102
+ const [newChecklistItemText, setNewChecklistItemText] = React.useState("");
100
103
  const [manualWorkstreamEntry, setManualWorkstreamEntry] = React.useState(false);
101
104
  const isArchivedReadOnly = Boolean(currentTask?.isArchived);
102
105
  const isDeletedReadOnly = Boolean(currentTask?.isDeleted);
103
- const workstreamOptions = React.useMemo(() => (workstreams.map((workstream) => {
106
+ const workstreamOptions = React.useMemo(() => workstreams.map((workstream) => {
104
107
  const reference = getWorkstreamReferenceLabel(workstream);
105
108
  return {
106
109
  value: reference || workstream.id,
107
- label: reference ? `${reference} · ${workstream.title}` : workstream.title,
108
- icon: 'Folder',
109
- color: 'var(--text-secondary)',
110
+ label: reference
111
+ ? `${reference} · ${workstream.title}`
112
+ : workstream.title,
113
+ icon: "Folder",
114
+ color: "var(--text-secondary)",
110
115
  };
111
- })), [workstreams]);
112
- const hasMatchingWorkstreamSelection = React.useMemo(() => (workstreamOptions.some((option) => String(option.value) === String(workstreamInput || '').trim())), [workstreamInput, workstreamOptions]);
116
+ }), [workstreams]);
117
+ const hasMatchingWorkstreamSelection = React.useMemo(() => workstreamOptions.some((option) => String(option.value) === String(workstreamInput || "").trim()), [workstreamInput, workstreamOptions]);
113
118
  React.useEffect(() => {
114
119
  if (editingTaskId) {
115
120
  if (manualWorkstreamEntry)
@@ -126,7 +131,12 @@ export function TaskForm(props) {
126
131
  if (!hasMatchingWorkstreamSelection && !manualWorkstreamEntry) {
127
132
  setManualWorkstreamEntry(true);
128
133
  }
129
- }, [editingTaskId, hasMatchingWorkstreamSelection, manualWorkstreamEntry, workstreamInput]);
134
+ }, [
135
+ editingTaskId,
136
+ hasMatchingWorkstreamSelection,
137
+ manualWorkstreamEntry,
138
+ workstreamInput,
139
+ ]);
130
140
  const isReadOnly = isArchivedReadOnly || isDeletedReadOnly;
131
141
  const formRef = React.useRef(null);
132
142
  const checklistSensors = useSensors(useSensor(PointerSensor, {
@@ -140,8 +150,8 @@ export function TaskForm(props) {
140
150
  form.scrollTop = 0;
141
151
  const container = form.parentElement;
142
152
  if (container) {
143
- if (typeof container.scrollTo === 'function') {
144
- container.scrollTo({ top: 0, left: 0, behavior: 'auto' });
153
+ if (typeof container.scrollTo === "function") {
154
+ container.scrollTo({ top: 0, left: 0, behavior: "auto" });
145
155
  }
146
156
  else {
147
157
  container.scrollTop = 0;
@@ -161,7 +171,7 @@ export function TaskForm(props) {
161
171
  ...checklistItems,
162
172
  {
163
173
  id: `checklist-draft-${now}-${checklistItems.length}`,
164
- taskId: editingTaskId || '',
174
+ taskId: editingTaskId || "",
165
175
  title: text,
166
176
  isCompleted: false,
167
177
  order: checklistItems.length,
@@ -169,7 +179,7 @@ export function TaskForm(props) {
169
179
  updatedAt: now,
170
180
  },
171
181
  ]);
172
- setNewChecklistItemText('');
182
+ setNewChecklistItemText("");
173
183
  };
174
184
  const handleChecklistReorder = React.useCallback((event) => {
175
185
  const { active, over } = event;
@@ -188,25 +198,27 @@ export function TaskForm(props) {
188
198
  onChecklistItemsChange(reordered);
189
199
  }, [checklistItems, onChecklistItemsChange]);
190
200
  const formatAiAuthorLabel = (author) => {
191
- const raw = String(author || '').trim();
201
+ const raw = String(author || "").trim();
192
202
  const cleaned = author
193
203
  .trim()
194
- .replace(/^ai-profile-/i, '')
195
- .replace(/[-_]+/g, ' ')
196
- .replace(/\s+/g, ' ');
197
- if (!cleaned || cleaned === 'ai' || raw.toLowerCase().startsWith('ai-profile-'))
198
- return 'AI Agent';
204
+ .replace(/^ai-profile-/i, "")
205
+ .replace(/[-_]+/g, " ")
206
+ .replace(/\s+/g, " ");
207
+ if (!cleaned ||
208
+ cleaned === "ai" ||
209
+ raw.toLowerCase().startsWith("ai-profile-"))
210
+ return "AI Agent";
199
211
  const label = cleaned
200
- .split(' ')
212
+ .split(" ")
201
213
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
202
- .join(' ');
214
+ .join(" ");
203
215
  return `AI Agent - ${label}`;
204
216
  };
205
217
  const resolvedAssigneeOptions = ensureAssigneeOptionPresent(assigneeOptions, assignee);
206
218
  const currentAssigneeKind = getAssigneeKind(assignee);
207
219
  const currentAssigneeLabel = getAssigneeLabel(assignee, resolvedAssigneeOptions);
208
220
  const actorOptionByValue = React.useMemo(() => new Map(resolvedAssigneeOptions.map((option) => [String(option.value), option])), [resolvedAssigneeOptions]);
209
- const currentAssigneeOption = actorOptionByValue.get(String(assignee || 'unassigned'));
221
+ const currentAssigneeOption = actorOptionByValue.get(String(assignee || "unassigned"));
210
222
  const currentAssigneeColor = currentTask?.assigneeActor?.color || currentAssigneeOption?.color;
211
223
  const actorFallbackById = React.useMemo(() => {
212
224
  const entries = [
@@ -215,45 +227,74 @@ export function TaskForm(props) {
215
227
  ].filter(Boolean);
216
228
  return new Map(entries.map((actor) => [String(actor.id), actor]));
217
229
  }, [currentTask?.assigneeActor, currentTask?.createdByActor]);
230
+ const workstreamLabelById = React.useMemo(() => new Map(workstreams.map((workstream) => [
231
+ workstream.id,
232
+ getWorkstreamReferenceLabel(workstream) || workstream.id,
233
+ ])), [workstreams]);
234
+ const initiativeLabelById = React.useMemo(() => new Map(initiatives.map((initiative) => [
235
+ initiative.id,
236
+ getInitiativeReferenceLabel(initiative) || initiative.id,
237
+ ])), [initiatives]);
218
238
  const taskActivityItems = React.useMemo(() => {
219
- if (Array.isArray(currentTask?.activity) && currentTask.activity.length > 0) {
239
+ if (Array.isArray(currentTask?.activity) &&
240
+ currentTask.activity.length > 0) {
220
241
  return currentTask.activity;
221
242
  }
222
243
  return comments.map((comment) => ({
223
244
  id: `comment:${comment.id}`,
224
- type: 'comment',
245
+ type: "comment",
225
246
  timestamp: comment.timestamp,
226
- comment
247
+ comment,
227
248
  }));
228
249
  }, [comments, currentTask?.activity]);
229
250
  const formatActivityFieldValue = (field, value) => {
230
- if (field !== 'assignee')
251
+ const normalizedValue = String(value || "").trim();
252
+ if (!normalizedValue)
231
253
  return undefined;
232
- return getAssigneeLabel(String(value || '').trim(), resolvedAssigneeOptions);
254
+ if (field === "assignee") {
255
+ return getAssigneeLabel(normalizedValue, resolvedAssigneeOptions);
256
+ }
257
+ if (field === "workstreamId") {
258
+ return workstreamLabelById.get(normalizedValue);
259
+ }
260
+ if (field === "initiativeId") {
261
+ return initiativeLabelById.get(normalizedValue);
262
+ }
263
+ return undefined;
233
264
  };
234
265
  const resolveActorLabel = React.useCallback((actorId, actorType, actorLabel) => {
235
266
  if (actorLabel && actorLabel.trim())
236
267
  return actorLabel.trim();
237
- const raw = String(actorId || '').trim();
268
+ const raw = String(actorId || "").trim();
238
269
  if (!raw)
239
- return actorType === 'ai' ? 'AI Agent' : t('taskForm.you');
270
+ return actorType === "ai" ? "AI Agent" : t("taskForm.you");
240
271
  const fallback = actorFallbackById.get(raw);
241
272
  if (fallback?.label)
242
273
  return fallback.label;
243
- const option = actorOptionByValue.get(raw) || actorOptionByValue.get(raw.toLowerCase());
274
+ const option = actorOptionByValue.get(raw) ||
275
+ actorOptionByValue.get(raw.toLowerCase());
244
276
  if (option?.label)
245
277
  return option.label;
246
- if (actorType === 'ai')
278
+ if (actorType === "ai")
247
279
  return formatAiAuthorLabel(raw);
248
280
  return getAssigneeLabel(raw, resolvedAssigneeOptions) || raw;
249
- }, [actorFallbackById, actorOptionByValue, formatAiAuthorLabel, resolvedAssigneeOptions]);
281
+ }, [
282
+ actorFallbackById,
283
+ actorOptionByValue,
284
+ formatAiAuthorLabel,
285
+ resolvedAssigneeOptions,
286
+ ]);
250
287
  const latestActivityActorLabel = React.useMemo(() => {
251
288
  const latestItem = taskActivityItems[0];
252
289
  if (!latestItem)
253
290
  return null;
254
- if (latestItem.type === 'comment') {
291
+ if (latestItem.type === "comment") {
255
292
  const comment = latestItem.comment;
256
- return resolveActorLabel(comment.author, comment.actor?.kind === 'ai' ? 'ai' : comment.actor?.kind === 'human' ? 'human' : null, comment.actor?.label || null);
293
+ return resolveActorLabel(comment.author, comment.actor?.kind === "ai"
294
+ ? "ai"
295
+ : comment.actor?.kind === "human"
296
+ ? "human"
297
+ : null, comment.actor?.label || null);
257
298
  }
258
299
  const event = latestItem.event;
259
300
  return resolveActorLabel(event.actor, event.actorType, event.actorProfile?.label || null);
@@ -263,158 +304,272 @@ export function TaskForm(props) {
263
304
  if (!completedAt)
264
305
  return null;
265
306
  for (const item of taskActivityItems) {
266
- if (item.type !== 'event')
307
+ if (item.type !== "event")
267
308
  continue;
268
309
  const event = item.event;
269
310
  const nextStatus = event.details?.changes?.status?.to;
270
- if (nextStatus === 'done' || nextStatus === 'cancelled') {
311
+ if (nextStatus === "done" || nextStatus === "cancelled") {
271
312
  return resolveActorLabel(event.actor, event.actorType, event.actorProfile?.label || null);
272
313
  }
273
314
  }
274
315
  return latestActivityActorLabel;
275
- }, [currentTask?.completedAt, latestActivityActorLabel, resolveActorLabel, taskActivityItems]);
276
- const createdByLabel = currentTask?.createdByActor?.label
277
- || resolveActorLabel(currentTask?.createdBy || null, currentTask?.createdByActor?.kind === 'ai' ? 'ai' : currentTask?.createdByActor?.kind === 'human' ? 'human' : null, currentTask?.createdByActor?.label || null);
316
+ }, [
317
+ currentTask?.completedAt,
318
+ latestActivityActorLabel,
319
+ resolveActorLabel,
320
+ taskActivityItems,
321
+ ]);
322
+ const createdByLabel = currentTask?.createdByActor?.label ||
323
+ resolveActorLabel(currentTask?.createdBy || null, currentTask?.createdByActor?.kind === "ai"
324
+ ? "ai"
325
+ : currentTask?.createdByActor?.kind === "human"
326
+ ? "human"
327
+ : null, currentTask?.createdByActor?.label || null);
278
328
  const updatedByLabel = latestActivityActorLabel;
279
329
  const activityCount = taskActivityItems.length;
280
- return (_jsxs("form", { ref: formRef, onSubmit: onSubmit, className: `${styles.form} ${styles.appScrollbar} tf-scrollbar`, children: [error && _jsx("div", { className: formStyles.error, children: error }), isReadOnly && (_jsx("div", { className: `${formStyles.formNotice} tf-text-helper`, children: isDeletedReadOnly
281
- ? 'Deleted tasks are read-only. Restore the task to make changes.'
282
- : 'Archived tasks are read-only. Unarchive the task to make changes.' })), _jsxs("fieldset", { disabled: isReadOnly, className: formStyles.formFieldsetReset, children: [_jsxs("div", { className: styles.field, children: [_jsxs("div", { className: styles.labelRow, children: [_jsx("label", { className: styles.label, children: "Title" }), assignee && (_jsx("span", { title: `Assigned to: ${currentAssigneeLabel}`, className: [
283
- formStyles.assigneeIndicator,
284
- currentAssigneeKind === 'agent'
285
- ? formStyles.assigneeIndicatorAgent
286
- : currentAssigneeKind === 'member'
287
- ? formStyles.assigneeIndicatorUser
288
- : formStyles.assigneeIndicatorUnassigned
289
- ].join(' '), style: currentAssigneeColor ? { color: currentAssigneeColor } : undefined, children: currentAssigneeKind === 'agent'
290
- ? _jsx(Bot, { size: 20 })
291
- : currentAssigneeKind === 'member'
292
- ? _jsx(User, { size: 20 })
293
- : _jsx(HelpCircle, { size: 20 }) }))] }), _jsx("input", { type: "text", value: title, onChange: (e) => onTitleChange(e.target.value), placeholder: "What needs to be done?", className: styles.input, required: true, maxLength: 255, autoFocus: true })] }), _jsxs("div", { className: styles.field, children: [_jsxs("div", { className: styles.labelRow, children: [_jsx("label", { className: styles.label, children: "Description" }), _jsx("button", { type: "button", className: styles.helpLink, onClick: () => onShowMarkdownHelpChange(!showMarkdownHelp), title: "Markdown Help", tabIndex: -1, children: _jsx(HelpCircle, { size: 14 }) })] }), showMarkdownHelp && (_jsxs("div", { className: formStyles.markdownHelp, children: [_jsxs("div", { className: formStyles.helpHeader, children: [_jsx("span", { children: "Markdown Guide" }), _jsx("button", { onClick: () => onShowMarkdownHelpChange(false), className: formStyles.helpClose, children: _jsx(X, { size: 12 }) })] }), _jsxs("div", { className: formStyles.helpGrid, children: [_jsx("div", { children: _jsx("code", { children: "**bold**" }) }), _jsx("div", { children: _jsx("code", { children: "_italic_" }) }), _jsx("div", { children: _jsx("code", { children: "- list" }) }), _jsx("div", { children: _jsx("code", { children: "1. list" }) }), _jsx("div", { className: formStyles.helpGridItem, children: _jsx("code", { children: "`inline code`" }) }), _jsxs("div", { className: formStyles.helpGridItem, children: [_jsx("code", { children: "```" }), _jsx("br", {}), _jsx("code", { children: "code block" }), _jsx("br", {}), _jsx("code", { children: "```" })] }), _jsx("div", { children: _jsx("code", { children: "[link](url)" }) })] })] })), descriptionFocused || !description ? (_jsx("textarea", { className: `${styles.textarea} ${formStyles.descriptionSurface}`, placeholder: "What needs to be done? (Markdown supported)", value: description, onChange: (e) => onDescriptionChange(e.target.value), onFocus: () => onDescriptionFocusedChange(true), onBlur: () => onDescriptionFocusedChange(false), rows: 9, autoFocus: descriptionFocused })) : (_jsx("div", { className: `${styles.textarea} ${formStyles.descriptionSurface} ${formStyles.markdownPreview} ${formStyles.markdownPreviewContainer}`, onClick: () => onDescriptionFocusedChange(true), children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: description }) }))] }), _jsxs("div", { className: formStyles.compactMetaSection, children: [_jsxs("div", { className: formStyles.compactMetaGrid, children: [_jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { id: "category-label", className: styles.label, children: taxonomyDisplayLabels?.category || 'Category' }), _jsx(TaxonomyDropdown, { value: category, options: categoryOptions, onChange: (value) => onCategoryChange(String(value)), required: true, ariaLabelledBy: "category-label" })] }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { id: "type-label", className: styles.label, children: taxonomyDisplayLabels?.type || 'Type' }), _jsx(TaxonomyDropdown, { value: type, options: typeOptions, onChange: (value) => onTypeChange(String(value)), ariaLabelledBy: "type-label" })] }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { htmlFor: "task-form-scheduled-date", className: styles.label, children: t('taskForm.scheduledLabel') }), _jsx("input", { id: "task-form-scheduled-date", type: "date", value: scheduledDate, onChange: (e) => onScheduledDateChange(e.target.value), className: `${styles.input} ${styles.taskFormDateInput}` })] }), _jsx("div", { className: formStyles.compactMetaSpacer, "aria-hidden": "true" }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { id: "assignee-label", className: styles.label, children: "Assigned To" }), _jsx(TaxonomyDropdown, { value: assignee || 'unassigned', options: resolvedAssigneeOptions, onChange: (value) => onAssigneeChange(String(value)), ariaLabelledBy: "assignee-label" })] }), _jsx("div", { className: formStyles.compactMetaField, children: _jsx(LevelSelector, { label: taxonomyDisplayLabels?.priority || 'Priority', options: priorities, value: priority, onChange: onPriorityChange, type: "priority", showSelectedLabel: false }) }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { htmlFor: "task-form-due-date", className: styles.label, children: t('taskForm.dueLabel') }), _jsx("input", { id: "task-form-due-date", type: "date", value: dueDate, onChange: (e) => onDueDateChange(e.target.value), className: `${styles.input} ${styles.taskFormDateInput}` }), dueBeforeScheduledWarning && (_jsx("span", { className: `${formStyles.taskFormDateWarning} ${formStyles.compactMetaFieldHint}`, children: t('taskForm.dueBeforeScheduled') })), overdueWarning && (_jsx("span", { className: `${formStyles.taskFormDateError} ${formStyles.compactMetaFieldHint}`, children: t('taskForm.overdue') }))] }), manualComplexityEnabled && (_jsx("div", { className: formStyles.compactMetaField, children: _jsx(LevelSelector, { label: "Complexity", options: [
294
- { value: 1, label: 'Tiny', color: '#10b981', icon: 'Gauge' },
295
- { value: 2, label: 'Low', color: '#14b8a6', icon: 'Gauge' },
296
- { value: 3, label: 'Medium', color: '#3b82f6', icon: 'Gauge' },
297
- { value: 4, label: 'High', color: '#8b5cf6', icon: 'Gauge' },
298
- { value: 5, label: 'Epic', color: '#d946ef', icon: 'Gauge' },
299
- ], value: complexity, onChange: onComplexityChange, type: "general" }) }))] }), !editingTaskId && (_jsxs("div", { className: formStyles.createWorkstreamRow, children: [_jsx("label", { className: styles.label, children: "Attach to workstream" }), _jsxs("div", { className: styles.pathInputGroup, children: [manualWorkstreamEntry ? (_jsx("input", { type: "text", className: styles.input, value: workstreamInput, onChange: (e) => onWorkstreamInputChange(e.target.value), placeholder: "Type a workstream name or reference", "aria-label": "Attach to workstream" })) : (_jsx(TaxonomyDropdown, { value: workstreamInput, options: workstreamOptions, onChange: (value) => onWorkstreamInputChange(String(value)), placeholder: "Select workstream...", ariaLabel: "Attach to workstream", className: formStyles.createWorkstreamDropdown })), _jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: () => {
300
- if (manualWorkstreamEntry) {
301
- setManualWorkstreamEntry(false);
302
- if (!hasMatchingWorkstreamSelection) {
303
- onWorkstreamInputChange('');
304
- }
305
- return;
330
+ React.useLayoutEffect(() => {
331
+ if (!editingTaskId)
332
+ return;
333
+ const endNode = commentsEndRef.current;
334
+ if (!endNode)
335
+ return;
336
+ const thread = endNode.parentElement;
337
+ const scrollToLatest = () => {
338
+ if (typeof endNode.scrollIntoView === "function") {
339
+ endNode.scrollIntoView({ block: "end" });
340
+ return;
341
+ }
342
+ if (thread) {
343
+ thread.scrollTop = thread.scrollHeight;
344
+ }
345
+ };
346
+ scrollToLatest();
347
+ const frame = window.requestAnimationFrame(scrollToLatest);
348
+ return () => window.cancelAnimationFrame(frame);
349
+ }, [activityCount, commentsEndRef, currentTask?.id, editingTaskId]);
350
+ const activitySidebar = editingTaskId ? (_jsxs("aside", { className: formStyles.activitySidebar, "aria-label": t("taskForm.commentsSectionTitle"), children: [_jsxs("div", { className: formStyles.activitySidebarHeader, children: [_jsx("h2", { className: formStyles.activitySidebarTitle, children: t("taskForm.commentsSectionTitle") }), _jsx("span", { className: formStyles.activitySidebarCount, children: activityCount })] }), _jsxs("div", { className: `${formStyles.commentPanel} ${formStyles.activitySidebarPanel}`.trim(), children: [_jsxs("div", { className: formStyles.commentThread, children: [activityCount === 0 && (_jsx("div", { className: formStyles.emptyComments, children: t("taskForm.noComments") })), taskActivityItems.map((item) => {
351
+ if (item.type === "comment") {
352
+ const c = item.comment;
353
+ const authorRef = String(c.author || "").trim();
354
+ const normalizedAuthor = authorRef.toLowerCase();
355
+ const actorFallback = actorFallbackById.get(authorRef);
356
+ const actorOption = actorOptionByValue.get(authorRef) ||
357
+ actorOptionByValue.get(normalizedAuthor);
358
+ const resolvedActor = c.actor || actorFallback || actorOption || null;
359
+ const commentKind = c.actor?.kind === "human"
360
+ ? "member"
361
+ : c.actor?.kind === "ai"
362
+ ? "agent"
363
+ : actorFallback?.kind === "human"
364
+ ? "member"
365
+ : actorFallback?.kind === "ai"
366
+ ? "agent"
367
+ : actorOption?.kind ||
368
+ (normalizedAuthor === "" ||
369
+ normalizedAuthor === "user" ||
370
+ normalizedAuthor === "human"
371
+ ? "member"
372
+ : "agent");
373
+ const authorLabel = c.actor?.label ||
374
+ actorFallback?.label ||
375
+ actorOption?.label ||
376
+ (authorRef
377
+ ? getAssigneeLabel(authorRef, resolvedAssigneeOptions)
378
+ : "") ||
379
+ (commentKind === "agent"
380
+ ? formatAiAuthorLabel(authorRef || "ai")
381
+ : t("taskForm.you"));
382
+ const authorColor = c.actor?.color || actorFallback?.color || actorOption?.color;
383
+ const iconName = String(resolvedActor?.icon ||
384
+ (commentKind === "agent" ? "Bot" : "User"));
385
+ const CommentActorIcon = IconMap[iconName] || (commentKind === "agent" ? Bot : User);
386
+ return (_jsxs("div", { className: `${formStyles.comment} ${commentKind === "agent" ? formStyles.commentAi : formStyles.commentUser}`, children: [_jsxs("div", { className: formStyles.commentHeader, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(CommentActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(c.timestamp, {
387
+ month: "short",
388
+ day: "numeric",
389
+ hour: "2-digit",
390
+ minute: "2-digit",
391
+ }) })] }), _jsx("div", { className: formStyles.commentText, style: authorColor
392
+ ? {
393
+ background: `linear-gradient(135deg, ${hexToRgba(authorColor, 0.18)} 0%, ${hexToRgba(authorColor, 0.1)} 100%)`,
394
+ borderColor: hexToRgba(authorColor, 0.35),
306
395
  }
307
- setManualWorkstreamEntry(true);
308
- }, "aria-label": manualWorkstreamEntry ? 'Use workstream dropdown' : 'Enter workstream by name', title: manualWorkstreamEntry ? 'Use workstream dropdown' : 'Enter workstream by name', children: _jsx(Plus, { size: 14 }) })] })] }))] }), _jsxs("div", { className: formStyles.sectionBlock, children: [_jsx("div", { className: formStyles.taxonomyFieldsGrid, children: visibleTaxonomies.map(tax => {
309
- if (tax.id === 'approach' && tax.isSystem)
310
- return null; // Workflow stays hidden until the dedicated refactor lands.
311
- const selectedRawValue = formTaxonomies[tax.id];
312
- const selectedValues = Array.isArray(selectedRawValue)
313
- ? selectedRawValue.map((value) => String(value))
314
- : (selectedRawValue !== undefined && selectedRawValue !== null && selectedRawValue !== ''
315
- ? [String(selectedRawValue)]
316
- : []);
317
- const selectableOptions = tax.options.filter((option) => (option.status !== 'retired' || selectedValues.includes(String(option.value))));
318
- return (_jsxs("div", { className: styles.field, children: [_jsx("label", { className: styles.label, children: tax.label }), tax.description && (_jsx("div", { className: `${formStyles.settingsHint} tf-text-helper`, children: tax.description })), tax.widgetType === 'level' ? (_jsx(LevelSelector, { label: tax.label, options: selectableOptions.map((option) => ({
319
- ...option,
320
- label: option.status === 'retired' ? `${option.label} (Retired)` : option.label
321
- })), value: formTaxonomies[tax.id] || '', onChange: (val) => onTaxonomyChange(tax.id, val), type: "general", hideLabel: true })) : (tax.multiSelect ? (_jsx("div", { className: `${styles.dropdownList} ${formStyles.softSectionSurface}`, children: selectableOptions.map((opt) => {
322
- const optionValue = String(opt.value);
323
- const isSelected = selectedValues.includes(optionValue);
324
- return (_jsxs("button", { type: "button", className: `${formStyles.specialistChip} ${isSelected ? formStyles.specialistChipActive : ''}`, onClick: () => {
325
- const nextValues = isSelected
326
- ? selectedValues.filter((value) => value !== optionValue)
327
- : [...selectedValues, optionValue];
328
- onTaxonomyChange(tax.id, nextValues);
329
- }, title: isSelected ? 'Click to remove' : 'Click to add', children: [isSelected && _jsx(Check, { size: 12 }), opt.status === 'retired' ? `${opt.label} (Retired)` : opt.label] }, optionValue));
330
- }) })) : (_jsxs("div", { className: styles.fieldIconWrapper, style: {
331
- '--field-color': 'var(--text-primary)',
332
- '--field-border': 'rgba(255, 255, 255, 0.1)',
333
- '--field-bg': 'rgba(255, 255, 255, 0.02)'
334
- }, children: [(() => {
335
- const selectedOption = tax.options.find(o => String(o.value) === String(formTaxonomies[tax.id]));
336
- const IconComp = selectedOption?.icon && Icons[selectedOption.icon] ? Icons[selectedOption.icon] : HelpCircle;
337
- const color = selectedOption?.color || 'var(--text-secondary)';
338
- return (_jsx("div", { className: styles.fieldIcon, style: { color: resolveColor(color) }, children: _jsx(IconComp, { size: 16 }) }));
339
- })(), _jsxs("select", { value: formTaxonomies[tax.id] || '', onChange: (e) => onTaxonomyChange(tax.id, e.target.value), className: `${styles.select} ${styles.field_dynamic}`, required: tax.isRequired, children: [_jsxs("option", { value: "", children: ["Select ", tax.label, "..."] }), selectableOptions.map(opt => (_jsx("option", { value: opt.value, children: opt.status === 'retired' ? `${opt.label} (Retired)` : opt.label }, opt.value)))] })] })))] }, tax.id));
340
- }) }), checklistEnabled && (_jsxs("div", { className: styles.specialistSection, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => onShowChecklistChange(!showChecklist), title: showChecklist ? 'Hide checklist' : 'Show checklist', children: [_jsxs("label", { className: styles.label, children: ["Checklist ", checklistItems.length > 0 && `(${completedChecklistCount}/${checklistItems.length})`] }), showChecklist ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 })] }), showChecklist && (_jsxs("div", { className: `${styles.dropdownList} ${formStyles.stackedList} ${formStyles.stackedListSpaced}`, children: [_jsxs("div", { className: styles.pathInputGroup, children: [_jsx("input", { type: "text", className: styles.input, placeholder: "Add checklist item", value: newChecklistItemText, onChange: (e) => setNewChecklistItemText(e.target.value), onKeyDown: (e) => {
341
- if (e.key === 'Enter') {
342
- e.preventDefault();
343
- handleAddChecklistItem();
344
- }
345
- } }), _jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: handleAddChecklistItem, disabled: !newChecklistItemText.trim(), children: "Add" })] }), checklistItems.length > 0 ? (_jsx(DndContext, { sensors: checklistSensors, collisionDetection: closestCenter, onDragEnd: handleChecklistReorder, children: _jsx(SortableContext, { items: checklistItems.map((item) => item.id), strategy: verticalListSortingStrategy, children: checklistItems.map((item, index) => (_jsx(SortableChecklistRow, { id: item.id || `checklist-${index}`, item: item, onToggle: () => {
346
- const now = new Date().toISOString();
347
- onChecklistItemsChange(checklistItems.map((entry, entryIndex) => entryIndex === index
348
- ? { ...entry, isCompleted: !entry.isCompleted, updatedAt: now }
349
- : entry));
350
- }, onRemove: () => onChecklistItemsChange(checklistItems
351
- .filter((_, entryIndex) => entryIndex !== index)
352
- .map((entry, entryIndex) => ({ ...entry, order: entryIndex }))) }, item.id || `checklist-${index}`))) }) })) : (_jsx("div", { className: `${formStyles.settingsHint} tf-text-helper`, children: "No checklist items yet." }))] }))] }))] }), _jsxs("div", { children: [_jsx(TaskContextUpload, { taskId: editingTaskId, taskReferenceLabel: currentTaskReferenceLabel, workspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption }), editingTaskId && (_jsxs("div", { className: styles.specialistSection, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => onShowCommentsChange(!showComments), title: showComments ? t('taskForm.hideComments') : t('taskForm.showComments'), children: [_jsxs("label", { className: styles.label, children: [t('taskForm.commentsSectionTitle'), " ", activityCount > 0 && `(${activityCount})`] }), showComments ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 })] }), showComments && (_jsxs("div", { className: formStyles.commentPanel, children: [_jsxs("div", { className: formStyles.commentThread, children: [activityCount === 0 && (_jsx("div", { className: formStyles.emptyComments, children: t('taskForm.noComments') })), taskActivityItems.map((item) => {
353
- if (item.type === 'comment') {
354
- const c = item.comment;
355
- const authorRef = String(c.author || '').trim();
356
- const normalizedAuthor = authorRef.toLowerCase();
357
- const actorFallback = actorFallbackById.get(authorRef);
358
- const actorOption = actorOptionByValue.get(authorRef) || actorOptionByValue.get(normalizedAuthor);
359
- const resolvedActor = c.actor || actorFallback || actorOption || null;
360
- const commentKind = c.actor?.kind === 'human'
361
- ? 'member'
362
- : c.actor?.kind === 'ai'
363
- ? 'agent'
364
- : actorFallback?.kind === 'human'
365
- ? 'member'
366
- : actorFallback?.kind === 'ai'
367
- ? 'agent'
368
- : actorOption?.kind || ((normalizedAuthor === '' || normalizedAuthor === 'user' || normalizedAuthor === 'human') ? 'member' : 'agent');
369
- const authorLabel = c.actor?.label
370
- || actorFallback?.label
371
- || actorOption?.label
372
- || (authorRef ? getAssigneeLabel(authorRef, resolvedAssigneeOptions) : '')
373
- || (commentKind === 'agent' ? formatAiAuthorLabel(authorRef || 'ai') : t('taskForm.you'));
374
- const authorColor = c.actor?.color || actorFallback?.color || actorOption?.color;
375
- const iconName = String(resolvedActor?.icon || (commentKind === 'agent' ? 'Bot' : 'User'));
376
- const CommentActorIcon = IconMap[iconName]
377
- || (commentKind === 'agent' ? Bot : User);
378
- return (_jsxs("div", { className: `${formStyles.comment} ${commentKind === 'agent' ? formStyles.commentAi : formStyles.commentUser}`, children: [_jsxs("div", { className: formStyles.commentHeader, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(CommentActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(c.timestamp, {
379
- month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
380
- }) })] }), _jsx("div", { className: formStyles.commentText, style: authorColor ? {
381
- background: `linear-gradient(135deg, ${hexToRgba(authorColor, 0.18)} 0%, ${hexToRgba(authorColor, 0.10)} 100%)`,
382
- borderColor: hexToRgba(authorColor, 0.35),
383
- } : undefined, children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: c.text }) })] }, c.id));
384
- }
385
- const event = item.event;
386
- const actorRef = String(event.actor || '').trim();
387
- const normalizedAuthor = actorRef.toLowerCase();
388
- const actorFallback = actorFallbackById.get(actorRef);
389
- const actorOption = actorOptionByValue.get(actorRef) || actorOptionByValue.get(normalizedAuthor);
390
- const resolvedActor = event.actorProfile || actorFallback || actorOption || null;
391
- const eventKind = event.actorType === 'ai'
392
- ? 'agent'
393
- : event.actorType === 'human'
394
- ? 'member'
395
- : 'system';
396
- const authorLabel = event.actorProfile?.label
397
- || actorFallback?.label
398
- || actorOption?.label
399
- || (eventKind === 'system'
400
- ? 'System'
401
- : actorRef
402
- ? getAssigneeLabel(actorRef, resolvedAssigneeOptions)
403
- : eventKind === 'agent'
404
- ? formatAiAuthorLabel('ai')
405
- : t('taskForm.you'));
406
- const authorColor = event.actorProfile?.color || actorFallback?.color || actorOption?.color;
407
- const iconName = String(resolvedActor?.icon || (eventKind === 'agent' ? 'Bot' : eventKind === 'member' ? 'User' : 'ClipboardList'));
408
- const EventActorIcon = IconMap[iconName]
409
- || (eventKind === 'agent' ? Bot : eventKind === 'member' ? User : ClipboardList);
410
- const changeEntries = Object.entries(event.details?.changes || {});
411
- return (_jsxs("div", { className: `${formStyles.comment} ${formStyles.activityEvent} ${eventKind === 'system' ? formStyles.activityEventSystem : ''}`, children: [_jsxs("div", { className: `${formStyles.commentHeader} ${formStyles.activityEventHeader}`, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(EventActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(event.createdAt, {
412
- month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
413
- }) })] }), _jsxs("div", { className: formStyles.activityEventText, children: [_jsx("div", { children: summarizeTaskEvent(event, formatActivityFieldValue) }), changeEntries.length > 1 && (_jsx("div", { className: formStyles.activityEventChanges, children: changeEntries.map(([field, change]) => (_jsx("span", { className: formStyles.activityEventChange, children: summarizeTaskChange(field, change, formatActivityFieldValue) }, field))) }))] })] }, item.id));
414
- }), _jsx("div", { ref: commentsEndRef })] }), _jsxs("div", { className: formStyles.commentInputArea, children: [_jsx("textarea", { className: formStyles.commentInput, placeholder: t('taskForm.commentPlaceholder'), value: newCommentText, onChange: (e) => onNewCommentTextChange(e.target.value), onKeyDown: (e) => {
415
- if (e.key === 'Enter' && !e.shiftKey) {
416
- e.preventDefault();
417
- onAddComment();
396
+ : undefined, children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: c.text }) })] }, c.id));
397
+ }
398
+ const event = item.event;
399
+ const actorRef = String(event.actor || "").trim();
400
+ const normalizedAuthor = actorRef.toLowerCase();
401
+ const actorFallback = actorFallbackById.get(actorRef);
402
+ const actorOption = actorOptionByValue.get(actorRef) ||
403
+ actorOptionByValue.get(normalizedAuthor);
404
+ const resolvedActor = event.actorProfile || actorFallback || actorOption || null;
405
+ const eventKind = event.actorType === "ai"
406
+ ? "agent"
407
+ : event.actorType === "human"
408
+ ? "member"
409
+ : "system";
410
+ const authorLabel = event.actorProfile?.label ||
411
+ actorFallback?.label ||
412
+ actorOption?.label ||
413
+ (eventKind === "system"
414
+ ? "System"
415
+ : actorRef
416
+ ? getAssigneeLabel(actorRef, resolvedAssigneeOptions)
417
+ : eventKind === "agent"
418
+ ? formatAiAuthorLabel("ai")
419
+ : t("taskForm.you"));
420
+ const authorColor = event.actorProfile?.color ||
421
+ actorFallback?.color ||
422
+ actorOption?.color;
423
+ const iconName = String(resolvedActor?.icon ||
424
+ (eventKind === "agent"
425
+ ? "Bot"
426
+ : eventKind === "member"
427
+ ? "User"
428
+ : "ClipboardList"));
429
+ const EventActorIcon = IconMap[iconName] ||
430
+ (eventKind === "agent"
431
+ ? Bot
432
+ : eventKind === "member"
433
+ ? User
434
+ : ClipboardList);
435
+ const changeEntries = Object.entries(event.details?.changes || {});
436
+ return (_jsxs("div", { className: `${formStyles.comment} ${formStyles.activityEvent} ${eventKind === "system" ? formStyles.activityEventSystem : ""}`, children: [_jsxs("div", { className: `${formStyles.commentHeader} ${formStyles.activityEventHeader}`, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(EventActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(event.createdAt, {
437
+ month: "short",
438
+ day: "numeric",
439
+ hour: "2-digit",
440
+ minute: "2-digit",
441
+ }) })] }), _jsxs("div", { className: formStyles.activityEventText, children: [_jsx("div", { children: summarizeTaskEvent(event, formatActivityFieldValue) }), changeEntries.length > 1 && (_jsx("div", { className: formStyles.activityEventChanges, children: changeEntries.map(([field, change]) => (_jsx("span", { className: formStyles.activityEventChange, children: summarizeTaskChange(field, change, formatActivityFieldValue) }, field))) }))] })] }, item.id));
442
+ }), _jsx("div", { ref: commentsEndRef })] }), _jsxs("div", { className: formStyles.commentInputArea, children: [_jsx("textarea", { className: formStyles.commentInput, placeholder: t("taskForm.commentPlaceholder"), value: newCommentText, onChange: (e) => onNewCommentTextChange(e.target.value), onKeyDown: (e) => {
443
+ if (e.key === "Enter" && !e.shiftKey) {
444
+ e.preventDefault();
445
+ onAddComment();
446
+ }
447
+ }, rows: 1 }), _jsx("button", { type: "button", className: formStyles.sendCommentBtn, onClick: onAddComment, disabled: !newCommentText.trim(), "aria-label": "Send comment", title: "Send comment", children: _jsx(Send, { size: 16 }) })] })] })] })) : null;
448
+ return (_jsxs("form", { ref: formRef, onSubmit: onSubmit, className: `${styles.form} ${styles.appScrollbar} tf-scrollbar`, children: [error && _jsx("div", { className: formStyles.error, children: error }), isReadOnly && (_jsx("div", { className: `${formStyles.formNotice} tf-text-helper`, children: isDeletedReadOnly
449
+ ? "Deleted tasks are read-only. Restore the task to make changes."
450
+ : "Archived tasks are read-only. Unarchive the task to make changes." })), _jsxs("fieldset", { disabled: isReadOnly, className: `${formStyles.formFieldsetReset} ${editingTaskId ? formStyles.formLayoutWithSidebar : formStyles.formLayout}`.trim(), children: [_jsxs("div", { className: formStyles.formMainColumn, children: [_jsxs("div", { className: styles.field, children: [_jsxs("div", { className: styles.labelRow, children: [_jsx("label", { className: styles.label, children: "Title" }), assignee && (_jsx("span", { title: `Assigned to: ${currentAssigneeLabel}`, className: [
451
+ formStyles.assigneeIndicator,
452
+ currentAssigneeKind === "agent"
453
+ ? formStyles.assigneeIndicatorAgent
454
+ : currentAssigneeKind === "member"
455
+ ? formStyles.assigneeIndicatorUser
456
+ : formStyles.assigneeIndicatorUnassigned,
457
+ ].join(" "), style: currentAssigneeColor
458
+ ? { color: currentAssigneeColor }
459
+ : undefined, children: currentAssigneeKind === "agent" ? (_jsx(Bot, { size: 20 })) : currentAssigneeKind === "member" ? (_jsx(User, { size: 20 })) : (_jsx(HelpCircle, { size: 20 })) }))] }), _jsx("input", { type: "text", value: title, onChange: (e) => onTitleChange(e.target.value), placeholder: "What needs to be done?", className: styles.input, required: true, maxLength: 255, autoFocus: true })] }), _jsxs("div", { className: styles.field, children: [_jsxs("div", { className: styles.labelRow, children: [_jsx("label", { className: styles.label, children: "Description" }), _jsx("button", { type: "button", className: styles.helpLink, onClick: () => onShowMarkdownHelpChange(!showMarkdownHelp), title: "Markdown Help", tabIndex: -1, children: _jsx(HelpCircle, { size: 14 }) })] }), showMarkdownHelp && (_jsxs("div", { className: formStyles.markdownHelp, children: [_jsxs("div", { className: formStyles.helpHeader, children: [_jsx("span", { children: "Markdown Guide" }), _jsx("button", { onClick: () => onShowMarkdownHelpChange(false), className: formStyles.helpClose, children: _jsx(X, { size: 12 }) })] }), _jsxs("div", { className: formStyles.helpGrid, children: [_jsx("div", { children: _jsx("code", { children: "**bold**" }) }), _jsx("div", { children: _jsx("code", { children: "_italic_" }) }), _jsx("div", { children: _jsx("code", { children: "- list" }) }), _jsx("div", { children: _jsx("code", { children: "1. list" }) }), _jsx("div", { className: formStyles.helpGridItem, children: _jsx("code", { children: "`inline code`" }) }), _jsxs("div", { className: formStyles.helpGridItem, children: [_jsx("code", { children: "```" }), _jsx("br", {}), _jsx("code", { children: "code block" }), _jsx("br", {}), _jsx("code", { children: "```" })] }), _jsx("div", { children: _jsx("code", { children: "[link](url)" }) })] })] })), descriptionFocused || !description ? (_jsx("textarea", { className: `${styles.textarea} ${formStyles.descriptionSurface}`, placeholder: "What needs to be done? (Markdown supported)", value: description, onChange: (e) => onDescriptionChange(e.target.value), onFocus: () => onDescriptionFocusedChange(true), onBlur: () => onDescriptionFocusedChange(false), rows: 9, autoFocus: descriptionFocused })) : (_jsx("div", { className: `${styles.textarea} ${formStyles.descriptionSurface} ${formStyles.markdownPreview} ${formStyles.markdownPreviewContainer}`, onClick: () => onDescriptionFocusedChange(true), children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: description }) }))] }), _jsxs("div", { className: formStyles.compactMetaSection, children: [_jsxs("div", { className: formStyles.compactMetaGrid, children: [_jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { id: "category-label", className: styles.label, children: taxonomyDisplayLabels?.category || "Category" }), _jsx(TaxonomyDropdown, { value: category, options: categoryOptions, onChange: (value) => onCategoryChange(String(value)), required: true, ariaLabelledBy: "category-label" })] }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { id: "type-label", className: styles.label, children: taxonomyDisplayLabels?.type || "Type" }), _jsx(TaxonomyDropdown, { value: type, options: typeOptions, onChange: (value) => onTypeChange(String(value)), ariaLabelledBy: "type-label" })] }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { htmlFor: "task-form-scheduled-date", className: styles.label, children: t("taskForm.scheduledLabel") }), _jsx("input", { id: "task-form-scheduled-date", type: "date", value: scheduledDate, onChange: (e) => onScheduledDateChange(e.target.value), className: `${styles.input} ${styles.taskFormDateInput}` })] }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { id: "assignee-label", className: styles.label, children: "Assigned To" }), _jsx(TaxonomyDropdown, { value: assignee || "unassigned", options: resolvedAssigneeOptions, onChange: (value) => onAssigneeChange(String(value)), ariaLabelledBy: "assignee-label" })] }), _jsx("div", { className: formStyles.compactMetaField, children: _jsx(LevelSelector, { label: taxonomyDisplayLabels?.priority || "Priority", options: priorities, value: priority, onChange: onPriorityChange, type: "priority", showSelectedLabel: false }) }), _jsxs("div", { className: `${styles.field} ${formStyles.compactMetaField}`, children: [_jsx("label", { htmlFor: "task-form-due-date", className: styles.label, children: t("taskForm.dueLabel") }), _jsx("input", { id: "task-form-due-date", type: "date", value: dueDate, onChange: (e) => onDueDateChange(e.target.value), className: `${styles.input} ${styles.taskFormDateInput}` }), dueBeforeScheduledWarning && (_jsx("span", { className: `${formStyles.taskFormDateWarning} ${formStyles.compactMetaFieldHint}`, children: t("taskForm.dueBeforeScheduled") })), overdueWarning && (_jsx("span", { className: `${formStyles.taskFormDateError} ${formStyles.compactMetaFieldHint}`, children: t("taskForm.overdue") }))] }), manualComplexityEnabled && (_jsx("div", { className: formStyles.compactMetaField, children: _jsx(LevelSelector, { label: "Complexity", options: [
460
+ {
461
+ value: 1,
462
+ label: "Tiny",
463
+ color: "#10b981",
464
+ icon: "Gauge",
465
+ },
466
+ {
467
+ value: 2,
468
+ label: "Low",
469
+ color: "#14b8a6",
470
+ icon: "Gauge",
471
+ },
472
+ {
473
+ value: 3,
474
+ label: "Medium",
475
+ color: "#3b82f6",
476
+ icon: "Gauge",
477
+ },
478
+ {
479
+ value: 4,
480
+ label: "High",
481
+ color: "#8b5cf6",
482
+ icon: "Gauge",
483
+ },
484
+ {
485
+ value: 5,
486
+ label: "Epic",
487
+ color: "#d946ef",
488
+ icon: "Gauge",
489
+ },
490
+ ], value: complexity, onChange: onComplexityChange, type: "general" }) }))] }), !editingTaskId && (_jsxs("div", { className: formStyles.createWorkstreamRow, children: [_jsx("label", { className: styles.label, children: "Attach to workstream" }), _jsxs("div", { className: styles.pathInputGroup, children: [manualWorkstreamEntry ? (_jsx("input", { type: "text", className: styles.input, value: workstreamInput, onChange: (e) => onWorkstreamInputChange(e.target.value), placeholder: "Type a workstream name or reference", "aria-label": "Attach to workstream" })) : (_jsx(TaxonomyDropdown, { value: workstreamInput, options: workstreamOptions, onChange: (value) => onWorkstreamInputChange(String(value)), placeholder: "Select workstream...", ariaLabel: "Attach to workstream", className: formStyles.createWorkstreamDropdown })), _jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: () => {
491
+ if (manualWorkstreamEntry) {
492
+ setManualWorkstreamEntry(false);
493
+ if (!hasMatchingWorkstreamSelection) {
494
+ onWorkstreamInputChange("");
495
+ }
496
+ return;
418
497
  }
419
- }, rows: 1 }), _jsx("button", { type: "button", className: formStyles.sendCommentBtn, onClick: onAddComment, disabled: !newCommentText.trim(), children: _jsx(Send, { size: 16 }) })] })] }))] })), _jsx("div", { className: `${formStyles.taskFormLifecycle} ${formStyles.taskFormMetaDivider}`, children: _jsx("div", { className: formStyles.taskFormMetaGrid, children: currentTask && (_jsxs(_Fragment, { children: [_jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t('taskForm.createdLabel') }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: createdLabel || t('taskForm.emptyValue') }), _jsx("span", { className: formStyles.taskFormMetaActor, children: createdByLabel ? `by ${createdByLabel}` : t('taskForm.emptyValue') })] })] }), _jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t('taskForm.updatedLabel') }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: updatedLabel || t('taskForm.emptyValue') }), _jsx("span", { className: formStyles.taskFormMetaActor, children: updatedByLabel ? `by ${updatedByLabel}` : t('taskForm.emptyValue') })] })] }), _jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t('taskForm.completedLabel') }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: completedLabel || t('taskForm.emptyValue') }), _jsx("span", { className: formStyles.taskFormMetaActor, children: completedByLabel ? `by ${completedByLabel}` : t('taskForm.emptyValue') })] })] })] })) }) })] }), _jsxs("div", { className: formStyles.keyboardHint, children: ["Press ", _jsx("kbd", { children: navigator.platform.includes('Mac') ? '⌥' : 'Alt' }), " to toggle"] })] })] }));
498
+ setManualWorkstreamEntry(true);
499
+ }, "aria-label": manualWorkstreamEntry
500
+ ? "Use workstream dropdown"
501
+ : "Enter workstream by name", title: manualWorkstreamEntry
502
+ ? "Use workstream dropdown"
503
+ : "Enter workstream by name", children: _jsx(Plus, { size: 14 }) })] })] }))] }), _jsxs("div", { className: formStyles.sectionBlock, children: [_jsx("div", { className: formStyles.taxonomyFieldsGrid, children: visibleTaxonomies.map((tax) => {
504
+ if (tax.id === "approach" && tax.isSystem)
505
+ return null; // Workflow stays hidden until the dedicated refactor lands.
506
+ const selectedRawValue = formTaxonomies[tax.id];
507
+ const selectedValues = Array.isArray(selectedRawValue)
508
+ ? selectedRawValue.map((value) => String(value))
509
+ : selectedRawValue !== undefined &&
510
+ selectedRawValue !== null &&
511
+ selectedRawValue !== ""
512
+ ? [String(selectedRawValue)]
513
+ : [];
514
+ const selectableOptions = tax.options.filter((option) => option.status !== "retired" ||
515
+ selectedValues.includes(String(option.value)));
516
+ return (_jsxs("div", { className: styles.field, children: [_jsx("label", { className: styles.label, children: tax.label }), tax.description && (_jsx("div", { className: `${formStyles.settingsHint} tf-text-helper`, children: tax.description })), tax.widgetType === "level" ? (_jsx(LevelSelector, { label: tax.label, options: selectableOptions.map((option) => ({
517
+ ...option,
518
+ label: option.status === "retired"
519
+ ? `${option.label} (Retired)`
520
+ : option.label,
521
+ })), value: formTaxonomies[tax.id] || "", onChange: (val) => onTaxonomyChange(tax.id, val), type: "general", hideLabel: true })) : tax.multiSelect ? (_jsx("div", { className: `${styles.dropdownList} ${formStyles.softSectionSurface}`, children: selectableOptions.map((opt) => {
522
+ const optionValue = String(opt.value);
523
+ const isSelected = selectedValues.includes(optionValue);
524
+ return (_jsxs("button", { type: "button", className: `${formStyles.specialistChip} ${isSelected ? formStyles.specialistChipActive : ""}`, onClick: () => {
525
+ const nextValues = isSelected
526
+ ? selectedValues.filter((value) => value !== optionValue)
527
+ : [...selectedValues, optionValue];
528
+ onTaxonomyChange(tax.id, nextValues);
529
+ }, title: isSelected ? "Click to remove" : "Click to add", children: [isSelected && _jsx(Check, { size: 12 }), opt.status === "retired"
530
+ ? `${opt.label} (Retired)`
531
+ : opt.label] }, optionValue));
532
+ }) })) : (_jsxs("div", { className: styles.fieldIconWrapper, style: {
533
+ "--field-color": "var(--text-primary)",
534
+ "--field-border": "rgba(255, 255, 255, 0.1)",
535
+ "--field-bg": "rgba(255, 255, 255, 0.02)",
536
+ }, children: [(() => {
537
+ const selectedOption = tax.options.find((o) => String(o.value) ===
538
+ String(formTaxonomies[tax.id]));
539
+ const IconComp = selectedOption?.icon &&
540
+ Icons[selectedOption.icon]
541
+ ? Icons[selectedOption.icon]
542
+ : HelpCircle;
543
+ const color = selectedOption?.color || "var(--text-secondary)";
544
+ return (_jsx("div", { className: styles.fieldIcon, style: { color: resolveColor(color) }, children: _jsx(IconComp, { size: 16 }) }));
545
+ })(), _jsxs("select", { value: formTaxonomies[tax.id] || "", onChange: (e) => onTaxonomyChange(tax.id, e.target.value), className: `${styles.select} ${styles.field_dynamic}`, required: tax.isRequired, children: [_jsxs("option", { value: "", children: ["Select ", tax.label, "..."] }), selectableOptions.map((opt) => (_jsx("option", { value: opt.value, children: opt.status === "retired"
546
+ ? `${opt.label} (Retired)`
547
+ : opt.label }, opt.value)))] })] }))] }, tax.id));
548
+ }) }), checklistEnabled && (_jsxs("div", { className: styles.specialistSection, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => onShowChecklistChange(!showChecklist), title: showChecklist ? "Hide checklist" : "Show checklist", children: [_jsxs("label", { className: styles.label, children: ["Checklist", " ", checklistItems.length > 0 &&
549
+ `(${completedChecklistCount}/${checklistItems.length})`] }), showChecklist ? (_jsx(ChevronDown, { size: 14 })) : (_jsx(ChevronRight, { size: 14 }))] }), showChecklist && (_jsxs("div", { className: `${styles.dropdownList} ${formStyles.stackedList} ${formStyles.stackedListSpaced}`, children: [_jsxs("div", { className: styles.pathInputGroup, children: [_jsx("input", { type: "text", className: styles.input, placeholder: "Add checklist item", value: newChecklistItemText, onChange: (e) => setNewChecklistItemText(e.target.value), onKeyDown: (e) => {
550
+ if (e.key === "Enter") {
551
+ e.preventDefault();
552
+ handleAddChecklistItem();
553
+ }
554
+ } }), _jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: handleAddChecklistItem, disabled: !newChecklistItemText.trim(), children: "Add" })] }), checklistItems.length > 0 ? (_jsx(DndContext, { sensors: checklistSensors, collisionDetection: closestCenter, onDragEnd: handleChecklistReorder, children: _jsx(SortableContext, { items: checklistItems.map((item) => item.id), strategy: verticalListSortingStrategy, children: checklistItems.map((item, index) => (_jsx(SortableChecklistRow, { id: item.id || `checklist-${index}`, item: item, onToggle: () => {
555
+ const now = new Date().toISOString();
556
+ onChecklistItemsChange(checklistItems.map((entry, entryIndex) => entryIndex === index
557
+ ? {
558
+ ...entry,
559
+ isCompleted: !entry.isCompleted,
560
+ updatedAt: now,
561
+ }
562
+ : entry));
563
+ }, onRemove: () => onChecklistItemsChange(checklistItems
564
+ .filter((_, entryIndex) => entryIndex !== index)
565
+ .map((entry, entryIndex) => ({
566
+ ...entry,
567
+ order: entryIndex,
568
+ }))) }, item.id || `checklist-${index}`))) }) })) : (_jsx("div", { className: `${formStyles.settingsHint} tf-text-helper`, children: "No checklist items yet." }))] }))] }))] }), _jsxs("div", { children: [_jsx(TaskContextUpload, { taskId: editingTaskId, taskReferenceLabel: currentTaskReferenceLabel, workspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, contextFiles: contextFiles, onAddContextFile: onAddContextFile, onRemoveContextFile: onRemoveContextFile, onUpdateContextCaption: onUpdateContextCaption }), _jsx("div", { className: `${formStyles.taskFormLifecycle} ${formStyles.taskFormMetaDivider}`, children: _jsx("div", { className: formStyles.taskFormMetaGrid, children: currentTask && (_jsxs(_Fragment, { children: [_jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.createdLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: createdLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: createdByLabel
569
+ ? `by ${createdByLabel}`
570
+ : t("taskForm.emptyValue") })] })] }), _jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.updatedLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: updatedLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: updatedByLabel
571
+ ? `by ${updatedByLabel}`
572
+ : t("taskForm.emptyValue") })] })] }), _jsxs("div", { className: formStyles.taskFormDateRow, children: [_jsx("span", { className: formStyles.taskFormDateLabel, children: t("taskForm.completedLabel") }), _jsxs("span", { className: formStyles.taskFormMetaStack, children: [_jsx("span", { className: formStyles.taskFormDateValue, children: completedLabel || t("taskForm.emptyValue") }), _jsx("span", { className: formStyles.taskFormMetaActor, children: completedByLabel
573
+ ? `by ${completedByLabel}`
574
+ : t("taskForm.emptyValue") })] })] })] })) }) })] })] }), activitySidebar, _jsxs("div", { className: formStyles.keyboardHint, children: ["Press ", _jsx("kbd", { children: navigator.platform.includes("Mac") ? "⌥" : "Alt" }), " to toggle"] })] })] }));
420
575
  }