@taskforcehq/taskforce 0.3.304 → 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 (138) hide show
  1. package/README.md +140 -247
  2. package/dist/TaskforceCore.js +165 -70
  3. package/dist/TaskforceCore.test.js +799 -90
  4. package/dist/compat/workspaceSyncCompat.js +6 -0
  5. package/dist/components/features/DocumentIndex.d.ts +1 -1
  6. package/dist/components/features/DocumentIndex.js +1 -1
  7. package/dist/components/features/DocumentViewer.d.ts +1 -1
  8. package/dist/components/features/DocumentViewer.js +32 -9
  9. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  10. package/dist/components/features/DocumentWorkspace.js +12 -155
  11. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  12. package/dist/components/features/TaskSettings.js +29 -3
  13. package/dist/components/features/TaskSettings.test.js +42 -1
  14. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  15. package/dist/components/features/documentWorkspaceModel.js +57 -0
  16. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  17. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  18. package/dist/components/task/TaskForm.d.ts +8 -8
  19. package/dist/components/task/TaskForm.js +358 -221
  20. package/dist/components/task/TaskForm.test.js +456 -381
  21. package/dist/components/task/TaskKanban.js +2 -1
  22. package/dist/components/views/PlanComparisonPage.js +5 -8
  23. package/dist/components/views/PlanComparisonPage.test.js +51 -41
  24. package/dist/components/views/PlansPage.js +126 -84
  25. package/dist/components/views/PlansPage.test.js +308 -39
  26. package/dist/components/views/StandaloneLayout.js +28 -22
  27. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  28. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  29. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  30. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  31. package/dist/config/envSchema.js +1 -1
  32. package/dist/core/AiProfileService.d.ts +6 -1
  33. package/dist/core/AiProfileService.js +161 -16
  34. package/dist/core/AiProfileService.test.js +3 -1
  35. package/dist/core/AiProfiles.test.js +200 -0
  36. package/dist/core/AuthTokenService.test.d.ts +1 -0
  37. package/dist/core/AuthTokenService.test.js +78 -0
  38. package/dist/core/EntitlementsPolicy.test.js +75 -13
  39. package/dist/core/PlanEntitlementService.d.ts +7 -1
  40. package/dist/core/PlanEntitlementService.js +117 -42
  41. package/dist/core/PlanVersionPolicy.test.js +19 -26
  42. package/dist/core/SignupMonetizationSettings.test.js +46 -21
  43. package/dist/core/SystemAdmin.test.js +212 -56
  44. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  45. package/dist/core/Taskforce.d.ts +3 -0
  46. package/dist/core/Taskforce.js +50 -10
  47. package/dist/core/WorkspacePlanMode.test.js +12 -12
  48. package/dist/core/shared.d.ts +2 -0
  49. package/dist/core/shared.js +11 -0
  50. package/dist/hooks/sync/auth.js +3 -1
  51. package/dist/hooks/sync/transfers.d.ts +2 -1
  52. package/dist/hooks/sync/transfers.js +8 -4
  53. package/dist/hooks/useSyncOrchestrator.d.ts +7 -4
  54. package/dist/hooks/useSyncOrchestrator.js +75 -7
  55. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +80 -10
  56. package/dist/hooks/useTaskMutations.d.ts +2 -1
  57. package/dist/hooks/useTaskMutations.js +12 -1
  58. package/dist/hooks/useTaskMutations.test.js +30 -1
  59. package/dist/hooks/useTaskforce.d.ts +5 -2
  60. package/dist/hooks/useTaskforce.js +17 -5
  61. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  62. package/dist/hooks/useWorkspaceSyncController.d.ts +3 -1
  63. package/dist/hooks/useWorkspaceSyncController.js +11 -1
  64. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  65. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  66. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  67. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  68. package/dist/mcp/collaborationRegistrar.js +71 -0
  69. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  70. package/dist/mcp/documentAssetRegistrar.js +346 -0
  71. package/dist/mcp/runtime.js +2362 -3530
  72. package/dist/mcp/runtime.test.js +159 -0
  73. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  74. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  75. package/dist/mcp/toolCatalog.d.ts +35 -0
  76. package/dist/mcp/toolCatalog.js +3 -0
  77. package/dist/server/routes/admin.d.ts +1 -5
  78. package/dist/server/routes/admin.js +138 -61
  79. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  80. package/dist/server/routes/annotatedAttachments.js +1 -1
  81. package/dist/server/routes/auth.d.ts +1 -4
  82. package/dist/server/routes/auth.js +23 -63
  83. package/dist/server/routes/authSupport.d.ts +30 -0
  84. package/dist/server/routes/authSupport.js +81 -0
  85. package/dist/server/routes/billing.d.ts +1 -1
  86. package/dist/server/routes/billing.js +104 -188
  87. package/dist/server/routes/billing.test.js +214 -86
  88. package/dist/server/routes/documentReviews.d.ts +1 -1
  89. package/dist/server/routes/documentReviews.js +1 -1
  90. package/dist/server/routes/documents.d.ts +4 -2
  91. package/dist/server/routes/documents.js +73 -7
  92. package/dist/server/routes/primitives.d.ts +11 -0
  93. package/dist/server/routes/primitives.js +73 -0
  94. package/dist/server/routes/resources.d.ts +1 -1
  95. package/dist/server/routes/resources.js +1 -1
  96. package/dist/server/routes/shared.d.ts +1 -1
  97. package/dist/server/routes/sync.d.ts +1 -1
  98. package/dist/server/routes/sync.js +1 -1
  99. package/dist/server/routes/tasks.d.ts +1 -1
  100. package/dist/server/routes/tasks.js +1 -1
  101. package/dist/server/routes/workspaces.d.ts +1 -1
  102. package/dist/server/routes/workspaces.js +1 -1
  103. package/dist/server/routes.d.ts +2 -9
  104. package/dist/server/routes.js +76 -162
  105. package/dist/server/routes.test.js +672 -30
  106. package/dist/sync/contentSyncState.js +16 -7
  107. package/dist/sync/syncApplyHandlers.test.js +2 -0
  108. package/dist/sync/syncService.d.ts +1 -0
  109. package/dist/sync/syncService.js +8 -0
  110. package/dist/sync/syncService.test.js +14 -0
  111. package/dist/sync/workspaceSyncModel.d.ts +3 -0
  112. package/dist/sync/workspaceSyncModel.js +12 -3
  113. package/dist/sync/workspaceSyncModel.test.js +34 -0
  114. package/dist/sync/workspaceSyncState.d.ts +2 -0
  115. package/dist/sync/workspaceSyncState.js +1 -0
  116. package/dist/ui/assets/{AgentsModule-BLWVbuKP.js → AgentsModule-N2MnQBZk.js} +1 -1
  117. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  118. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  119. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  120. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  121. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  122. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  123. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BcS4afRn.js} +1 -1
  124. package/dist/ui/assets/{index-Ii0B0rTO.css → index-C2-OXZV7.css} +1 -1
  125. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  126. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-BSUaRwF8.js} +1 -1
  127. package/dist/ui/index.html +3 -3
  128. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  129. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  130. package/package.json +8 -1
  131. package/scripts/check-cloud-mcp.mjs +83 -0
  132. package/scripts/playwright-auth.sh +5 -1
  133. package/scripts/run-billing-performance-smoke.sh +24 -0
  134. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  135. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  136. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  137. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  138. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
@@ -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 { getInitiativeReferenceLabel, 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 = [], initiatives = [], 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 = [
@@ -224,27 +236,28 @@ export function TaskForm(props) {
224
236
  getInitiativeReferenceLabel(initiative) || initiative.id,
225
237
  ])), [initiatives]);
226
238
  const taskActivityItems = React.useMemo(() => {
227
- if (Array.isArray(currentTask?.activity) && currentTask.activity.length > 0) {
239
+ if (Array.isArray(currentTask?.activity) &&
240
+ currentTask.activity.length > 0) {
228
241
  return currentTask.activity;
229
242
  }
230
243
  return comments.map((comment) => ({
231
244
  id: `comment:${comment.id}`,
232
- type: 'comment',
245
+ type: "comment",
233
246
  timestamp: comment.timestamp,
234
- comment
247
+ comment,
235
248
  }));
236
249
  }, [comments, currentTask?.activity]);
237
250
  const formatActivityFieldValue = (field, value) => {
238
- const normalizedValue = String(value || '').trim();
251
+ const normalizedValue = String(value || "").trim();
239
252
  if (!normalizedValue)
240
253
  return undefined;
241
- if (field === 'assignee') {
254
+ if (field === "assignee") {
242
255
  return getAssigneeLabel(normalizedValue, resolvedAssigneeOptions);
243
256
  }
244
- if (field === 'workstreamId') {
257
+ if (field === "workstreamId") {
245
258
  return workstreamLabelById.get(normalizedValue);
246
259
  }
247
- if (field === 'initiativeId') {
260
+ if (field === "initiativeId") {
248
261
  return initiativeLabelById.get(normalizedValue);
249
262
  }
250
263
  return undefined;
@@ -252,26 +265,36 @@ export function TaskForm(props) {
252
265
  const resolveActorLabel = React.useCallback((actorId, actorType, actorLabel) => {
253
266
  if (actorLabel && actorLabel.trim())
254
267
  return actorLabel.trim();
255
- const raw = String(actorId || '').trim();
268
+ const raw = String(actorId || "").trim();
256
269
  if (!raw)
257
- return actorType === 'ai' ? 'AI Agent' : t('taskForm.you');
270
+ return actorType === "ai" ? "AI Agent" : t("taskForm.you");
258
271
  const fallback = actorFallbackById.get(raw);
259
272
  if (fallback?.label)
260
273
  return fallback.label;
261
- const option = actorOptionByValue.get(raw) || actorOptionByValue.get(raw.toLowerCase());
274
+ const option = actorOptionByValue.get(raw) ||
275
+ actorOptionByValue.get(raw.toLowerCase());
262
276
  if (option?.label)
263
277
  return option.label;
264
- if (actorType === 'ai')
278
+ if (actorType === "ai")
265
279
  return formatAiAuthorLabel(raw);
266
280
  return getAssigneeLabel(raw, resolvedAssigneeOptions) || raw;
267
- }, [actorFallbackById, actorOptionByValue, formatAiAuthorLabel, resolvedAssigneeOptions]);
281
+ }, [
282
+ actorFallbackById,
283
+ actorOptionByValue,
284
+ formatAiAuthorLabel,
285
+ resolvedAssigneeOptions,
286
+ ]);
268
287
  const latestActivityActorLabel = React.useMemo(() => {
269
288
  const latestItem = taskActivityItems[0];
270
289
  if (!latestItem)
271
290
  return null;
272
- if (latestItem.type === 'comment') {
291
+ if (latestItem.type === "comment") {
273
292
  const comment = latestItem.comment;
274
- 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);
275
298
  }
276
299
  const event = latestItem.event;
277
300
  return resolveActorLabel(event.actor, event.actorType, event.actorProfile?.label || null);
@@ -281,158 +304,272 @@ export function TaskForm(props) {
281
304
  if (!completedAt)
282
305
  return null;
283
306
  for (const item of taskActivityItems) {
284
- if (item.type !== 'event')
307
+ if (item.type !== "event")
285
308
  continue;
286
309
  const event = item.event;
287
310
  const nextStatus = event.details?.changes?.status?.to;
288
- if (nextStatus === 'done' || nextStatus === 'cancelled') {
311
+ if (nextStatus === "done" || nextStatus === "cancelled") {
289
312
  return resolveActorLabel(event.actor, event.actorType, event.actorProfile?.label || null);
290
313
  }
291
314
  }
292
315
  return latestActivityActorLabel;
293
- }, [currentTask?.completedAt, latestActivityActorLabel, resolveActorLabel, taskActivityItems]);
294
- const createdByLabel = currentTask?.createdByActor?.label
295
- || 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);
296
328
  const updatedByLabel = latestActivityActorLabel;
297
329
  const activityCount = taskActivityItems.length;
298
- 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
299
- ? 'Deleted tasks are read-only. Restore the task to make changes.'
300
- : '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: [
301
- formStyles.assigneeIndicator,
302
- currentAssigneeKind === 'agent'
303
- ? formStyles.assigneeIndicatorAgent
304
- : currentAssigneeKind === 'member'
305
- ? formStyles.assigneeIndicatorUser
306
- : formStyles.assigneeIndicatorUnassigned
307
- ].join(' '), style: currentAssigneeColor ? { color: currentAssigneeColor } : undefined, children: currentAssigneeKind === 'agent'
308
- ? _jsx(Bot, { size: 20 })
309
- : currentAssigneeKind === 'member'
310
- ? _jsx(User, { size: 20 })
311
- : _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: [
312
- { value: 1, label: 'Tiny', color: '#10b981', icon: 'Gauge' },
313
- { value: 2, label: 'Low', color: '#14b8a6', icon: 'Gauge' },
314
- { value: 3, label: 'Medium', color: '#3b82f6', icon: 'Gauge' },
315
- { value: 4, label: 'High', color: '#8b5cf6', icon: 'Gauge' },
316
- { value: 5, label: 'Epic', color: '#d946ef', icon: 'Gauge' },
317
- ], 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: () => {
318
- if (manualWorkstreamEntry) {
319
- setManualWorkstreamEntry(false);
320
- if (!hasMatchingWorkstreamSelection) {
321
- onWorkstreamInputChange('');
322
- }
323
- 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),
324
395
  }
325
- setManualWorkstreamEntry(true);
326
- }, "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 => {
327
- if (tax.id === 'approach' && tax.isSystem)
328
- return null; // Workflow stays hidden until the dedicated refactor lands.
329
- const selectedRawValue = formTaxonomies[tax.id];
330
- const selectedValues = Array.isArray(selectedRawValue)
331
- ? selectedRawValue.map((value) => String(value))
332
- : (selectedRawValue !== undefined && selectedRawValue !== null && selectedRawValue !== ''
333
- ? [String(selectedRawValue)]
334
- : []);
335
- const selectableOptions = tax.options.filter((option) => (option.status !== 'retired' || selectedValues.includes(String(option.value))));
336
- 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) => ({
337
- ...option,
338
- label: option.status === 'retired' ? `${option.label} (Retired)` : option.label
339
- })), 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) => {
340
- const optionValue = String(opt.value);
341
- const isSelected = selectedValues.includes(optionValue);
342
- return (_jsxs("button", { type: "button", className: `${formStyles.specialistChip} ${isSelected ? formStyles.specialistChipActive : ''}`, onClick: () => {
343
- const nextValues = isSelected
344
- ? selectedValues.filter((value) => value !== optionValue)
345
- : [...selectedValues, optionValue];
346
- onTaxonomyChange(tax.id, nextValues);
347
- }, title: isSelected ? 'Click to remove' : 'Click to add', children: [isSelected && _jsx(Check, { size: 12 }), opt.status === 'retired' ? `${opt.label} (Retired)` : opt.label] }, optionValue));
348
- }) })) : (_jsxs("div", { className: styles.fieldIconWrapper, style: {
349
- '--field-color': 'var(--text-primary)',
350
- '--field-border': 'rgba(255, 255, 255, 0.1)',
351
- '--field-bg': 'rgba(255, 255, 255, 0.02)'
352
- }, children: [(() => {
353
- const selectedOption = tax.options.find(o => String(o.value) === String(formTaxonomies[tax.id]));
354
- const IconComp = selectedOption?.icon && Icons[selectedOption.icon] ? Icons[selectedOption.icon] : HelpCircle;
355
- const color = selectedOption?.color || 'var(--text-secondary)';
356
- return (_jsx("div", { className: styles.fieldIcon, style: { color: resolveColor(color) }, children: _jsx(IconComp, { size: 16 }) }));
357
- })(), _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));
358
- }) }), 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) => {
359
- if (e.key === 'Enter') {
360
- e.preventDefault();
361
- handleAddChecklistItem();
362
- }
363
- } }), _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: () => {
364
- const now = new Date().toISOString();
365
- onChecklistItemsChange(checklistItems.map((entry, entryIndex) => entryIndex === index
366
- ? { ...entry, isCompleted: !entry.isCompleted, updatedAt: now }
367
- : entry));
368
- }, onRemove: () => onChecklistItemsChange(checklistItems
369
- .filter((_, entryIndex) => entryIndex !== index)
370
- .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) => {
371
- if (item.type === 'comment') {
372
- const c = item.comment;
373
- const authorRef = String(c.author || '').trim();
374
- const normalizedAuthor = authorRef.toLowerCase();
375
- const actorFallback = actorFallbackById.get(authorRef);
376
- const actorOption = actorOptionByValue.get(authorRef) || actorOptionByValue.get(normalizedAuthor);
377
- const resolvedActor = c.actor || actorFallback || actorOption || null;
378
- const commentKind = c.actor?.kind === 'human'
379
- ? 'member'
380
- : c.actor?.kind === 'ai'
381
- ? 'agent'
382
- : actorFallback?.kind === 'human'
383
- ? 'member'
384
- : actorFallback?.kind === 'ai'
385
- ? 'agent'
386
- : actorOption?.kind || ((normalizedAuthor === '' || normalizedAuthor === 'user' || normalizedAuthor === 'human') ? 'member' : 'agent');
387
- const authorLabel = c.actor?.label
388
- || actorFallback?.label
389
- || actorOption?.label
390
- || (authorRef ? getAssigneeLabel(authorRef, resolvedAssigneeOptions) : '')
391
- || (commentKind === 'agent' ? formatAiAuthorLabel(authorRef || 'ai') : t('taskForm.you'));
392
- const authorColor = c.actor?.color || actorFallback?.color || actorOption?.color;
393
- const iconName = String(resolvedActor?.icon || (commentKind === 'agent' ? 'Bot' : 'User'));
394
- const CommentActorIcon = IconMap[iconName]
395
- || (commentKind === 'agent' ? Bot : User);
396
- 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, {
397
- month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
398
- }) })] }), _jsx("div", { className: formStyles.commentText, style: authorColor ? {
399
- background: `linear-gradient(135deg, ${hexToRgba(authorColor, 0.18)} 0%, ${hexToRgba(authorColor, 0.10)} 100%)`,
400
- borderColor: hexToRgba(authorColor, 0.35),
401
- } : undefined, children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: c.text }) })] }, c.id));
402
- }
403
- const event = item.event;
404
- const actorRef = String(event.actor || '').trim();
405
- const normalizedAuthor = actorRef.toLowerCase();
406
- const actorFallback = actorFallbackById.get(actorRef);
407
- const actorOption = actorOptionByValue.get(actorRef) || actorOptionByValue.get(normalizedAuthor);
408
- const resolvedActor = event.actorProfile || actorFallback || actorOption || null;
409
- const eventKind = event.actorType === 'ai'
410
- ? 'agent'
411
- : event.actorType === 'human'
412
- ? 'member'
413
- : 'system';
414
- const authorLabel = event.actorProfile?.label
415
- || actorFallback?.label
416
- || actorOption?.label
417
- || (eventKind === 'system'
418
- ? 'System'
419
- : actorRef
420
- ? getAssigneeLabel(actorRef, resolvedAssigneeOptions)
421
- : eventKind === 'agent'
422
- ? formatAiAuthorLabel('ai')
423
- : t('taskForm.you'));
424
- const authorColor = event.actorProfile?.color || actorFallback?.color || actorOption?.color;
425
- const iconName = String(resolvedActor?.icon || (eventKind === 'agent' ? 'Bot' : eventKind === 'member' ? 'User' : 'ClipboardList'));
426
- const EventActorIcon = IconMap[iconName]
427
- || (eventKind === 'agent' ? Bot : eventKind === 'member' ? User : ClipboardList);
428
- const changeEntries = Object.entries(event.details?.changes || {});
429
- 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, {
430
- month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
431
- }) })] }), _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));
432
- }), _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) => {
433
- if (e.key === 'Enter' && !e.shiftKey) {
434
- e.preventDefault();
435
- 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;
436
497
  }
437
- }, 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"] })] })] }));
438
575
  }