@taskforcehq/taskforce 0.3.327 → 0.3.328

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 (187) hide show
  1. package/dist/Taskforce.module.css +24 -53
  2. package/dist/components/activity/EntityActivityTimeline.js +15 -5
  3. package/dist/components/context/AttachmentPreviewHost.js +1 -1
  4. package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
  5. package/dist/components/context/ContextAttachmentManager.js +116 -20
  6. package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
  7. package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
  8. package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
  9. package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
  10. package/dist/components/features/planning/PlanningModule.js +26 -9
  11. package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
  12. package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
  13. package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
  14. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  15. package/dist/components/task/TaskActionHeader.js +2 -2
  16. package/dist/components/task/TaskCard.js +37 -4
  17. package/dist/components/task/TaskContextUpload.d.ts +3 -0
  18. package/dist/components/task/TaskContextUpload.js +2 -2
  19. package/dist/components/task/TaskForm.d.ts +4 -0
  20. package/dist/components/task/TaskForm.js +32 -7
  21. package/dist/components/task/TaskImageReviews.js +16 -16
  22. package/dist/components/task/TaskStatusActions.js +11 -2
  23. package/dist/components/task/TaskWorkstreamPicker.js +2 -1
  24. package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
  25. package/dist/components/ui/DocumentTypeIcon.js +8 -0
  26. package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
  27. package/dist/components/ui/TaxonomyDropdown.js +30 -3
  28. package/dist/components/views/StandaloneLayout.js +62 -9
  29. package/dist/components/views/WidgetView.js +8 -5
  30. package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
  31. package/dist/components/views/panels/PlanningDrawer.js +48 -44
  32. package/dist/config/envSchema.js +1 -0
  33. package/dist/config/localServiceCloudTarget.d.ts +10 -0
  34. package/dist/config/localServiceCloudTarget.js +76 -0
  35. package/dist/core/AiProfileService.d.ts +1 -0
  36. package/dist/core/AiProfileService.js +2 -1
  37. package/dist/core/AttachmentLinkService.js +7 -4
  38. package/dist/core/DeletedTaskLifecycleService.d.ts +4 -0
  39. package/dist/core/DeletedTaskLifecycleService.js +21 -0
  40. package/dist/core/McpTokenService.d.ts +4 -0
  41. package/dist/core/McpTokenService.js +52 -0
  42. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +5 -0
  43. package/dist/core/TaskAttachmentLinksDurableMutationService.js +20 -11
  44. package/dist/core/Taskforce.d.ts +63 -1
  45. package/dist/core/Taskforce.js +558 -73
  46. package/dist/core/types.d.ts +2 -1
  47. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
  48. package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
  49. package/dist/documentReviews/service.d.ts +4 -0
  50. package/dist/documentReviews/service.js +141 -46
  51. package/dist/hooks/tasks/useTaskFormActions.d.ts +3 -0
  52. package/dist/hooks/tasks/useTaskFormActions.js +33 -7
  53. package/dist/hooks/useTaskforce.d.ts +59 -50
  54. package/dist/hooks/useTaskforce.js +103 -1
  55. package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
  56. package/dist/mcp/canonicalAssetHelpers.js +39 -5
  57. package/dist/mcp/collaborationRegistrar.js +15 -13
  58. package/dist/mcp/documentAssetRegistrar.js +16 -9
  59. package/dist/mcp/localServiceProxy.js +4 -1
  60. package/dist/mcp/runtime.d.ts +16 -0
  61. package/dist/mcp/runtime.js +566 -171
  62. package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
  63. package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
  64. package/dist/mcp/taskPlanningRegistrar.js +42 -34
  65. package/dist/mcp/toolRegistry.js +2 -2
  66. package/dist/migrations/taskSchemaMigrations.js +35 -0
  67. package/dist/runtime/appGateDefinitions.d.ts +2 -2
  68. package/dist/runtime/appGateDefinitions.js +2 -2
  69. package/dist/server/appAccess.d.ts +12 -0
  70. package/dist/server/appAccess.js +21 -0
  71. package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
  72. package/dist/server/authorizedWorkspaceAccess.js +40 -0
  73. package/dist/server/cloudCollectionRead.d.ts +25 -0
  74. package/dist/server/cloudCollectionRead.js +120 -0
  75. package/dist/server/cloudCollectionReadPool.d.ts +52 -0
  76. package/dist/server/cloudCollectionReadPool.js +140 -0
  77. package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
  78. package/dist/server/cloudCollectionReadWorker.js +36 -0
  79. package/dist/server/index.d.ts +3 -11
  80. package/dist/server/index.js +53 -40
  81. package/dist/server/localServiceLease.d.ts +3 -0
  82. package/dist/server/localServiceLease.js +9 -1
  83. package/dist/server/routes/documents.d.ts +5 -2
  84. package/dist/server/routes/documents.js +160 -13
  85. package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
  86. package/dist/server/routes/durableTaskHttpRouters.js +2 -2
  87. package/dist/server/routes/localService.js +2 -0
  88. package/dist/server/routes/primitives.d.ts +2 -0
  89. package/dist/server/routes/shared.d.ts +24 -0
  90. package/dist/server/routes/shared.js +114 -18
  91. package/dist/server/routes/sync.js +22 -0
  92. package/dist/server/routes/syncV3FeedRoutes.js +4 -2
  93. package/dist/server/routes/syncV3LocalCaptureRoutes.js +20 -2
  94. package/dist/server/routes/syncV3OperationRoutes.js +17 -2
  95. package/dist/server/routes/tasks.js +199 -89
  96. package/dist/server/routes.js +35 -7
  97. package/dist/service/localServiceCli.js +36 -25
  98. package/dist/service/localServiceClientLifecycle.js +67 -6
  99. package/dist/shared/planningIdentity.d.ts +20 -0
  100. package/dist/shared/planningIdentity.js +79 -0
  101. package/dist/storage/postgresAdapter.js +4 -0
  102. package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
  103. package/dist/storage/postgresRequestDiagnostics.js +24 -0
  104. package/dist/storage/workspaceAssetStore.d.ts +32 -0
  105. package/dist/storage/workspaceAssetStore.js +70 -0
  106. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +16 -0
  107. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +70 -22
  108. package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
  109. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +5 -0
  110. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
  111. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +15 -0
  112. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +26 -3
  113. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
  114. package/dist/sync/syncApplyHandlers.d.ts +3 -1
  115. package/dist/sync/syncApplyHandlers.js +29 -0
  116. package/dist/sync/syncService.js +9 -0
  117. package/dist/sync/taskSyncPayload.d.ts +1 -0
  118. package/dist/sync/taskSyncPayload.js +6 -0
  119. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +1 -0
  120. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +21 -3
  121. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +17 -4
  122. package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
  123. package/dist/sync/v3/localOutboxDispatcher.js +5 -1
  124. package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
  125. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +1 -0
  126. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +15 -3
  127. package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
  128. package/dist/sync/v3/syncDurabilityStore.d.ts +8 -0
  129. package/dist/sync/v3/syncDurabilityStore.js +171 -33
  130. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +1 -0
  131. package/dist/sync/v3/taskAttachmentLinksMutationService.js +18 -2
  132. package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
  133. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +1 -9
  134. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +1 -0
  135. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +7 -1
  136. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +15 -3
  137. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +4 -0
  138. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +29 -6
  139. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +1 -0
  140. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +29 -7
  141. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +62 -7
  142. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +1 -0
  143. package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
  144. package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
  145. package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
  146. package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
  147. package/dist/sync/workspacePullFeed.d.ts +6 -1
  148. package/dist/sync/workspacePullFeed.js +172 -161
  149. package/dist/sync/workspaceSyncModel.d.ts +11 -0
  150. package/dist/sync/workspaceSyncModel.js +27 -2
  151. package/dist/types.d.ts +8 -0
  152. package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-MZs7lVED.js} +1 -1
  153. package/dist/ui/assets/{AiProfilesModule-DPxQeF9N.js → AiProfilesModule-C3i0LOxe.js} +1 -1
  154. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DHNYaNg6.js → AnnotatedAttachmentWorkspace-DVZk5Tln.js} +1 -1
  155. package/dist/ui/assets/AssetTraySortControl-Kf1pelSy.js +1 -0
  156. package/dist/ui/assets/ContextAttachmentManager-B8boOs6Q.js +3 -0
  157. package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-DXH4XUsB.js} +4 -4
  158. package/dist/ui/assets/EntityActivityTimeline-BtSTq0ON.js +1 -0
  159. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +1 -0
  160. package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
  161. package/dist/ui/assets/{PlansPage-BVH_NfgT.js → PlansPage-ChqgxALc.js} +1 -1
  162. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +1 -0
  163. package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
  164. package/dist/ui/assets/{TaskSettings-xVsxSa0g.js → TaskSettings-g4ECKNOz.js} +1 -1
  165. package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BazdUwxZ.js} +1 -1
  166. package/dist/ui/assets/WorkflowManagerModule-DWFspC-o.js +1 -0
  167. package/dist/ui/assets/index-BUplSsv_.js +7 -0
  168. package/dist/ui/assets/index-gz2EXuoK.css +1 -0
  169. package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-BkFLXavV.js} +1 -1
  170. package/dist/ui/index.html +3 -3
  171. package/dist/utils/constants.d.ts +2 -2
  172. package/dist/utils/constants.js +1 -1
  173. package/dist/utils/contextAttachmentUpload.d.ts +2 -0
  174. package/dist/utils/contextAttachmentUpload.js +7 -0
  175. package/dist/utils/contextFiles.d.ts +1 -1
  176. package/dist/utils/contextFiles.js +4 -4
  177. package/package.json +6 -1
  178. package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
  179. package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
  180. package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
  181. package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
  182. package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
  183. package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
  184. package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
  185. package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
  186. package/dist/ui/assets/index-BzJlVnNS.css +0 -1
  187. package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
@@ -959,6 +959,15 @@
959
959
  transform: scale(1.1);
960
960
  }
961
961
 
962
+ .restoreTaskBtn {
963
+ background: rgba(59, 130, 246, 0.1) !important;
964
+ color: #3b82f6 !important;
965
+ }
966
+
967
+ .restoreTaskBtn:hover {
968
+ background: rgba(59, 130, 246, 0.25) !important;
969
+ }
970
+
962
971
  .startWorkBtn {
963
972
  background: rgba(59, 130, 246, 0.1) !important;
964
973
  color: #3b82f6 !important;
@@ -997,15 +1006,6 @@
997
1006
  }
998
1007
 
999
1008
 
1000
- .archiveTaskBtn {
1001
- background: rgba(59, 130, 246, 0.1) !important;
1002
- color: #3b82f6 !important;
1003
- }
1004
-
1005
- .archiveTaskBtn:hover {
1006
- background: rgba(59, 130, 246, 0.25) !important;
1007
- }
1008
-
1009
1009
  .bulkArchiveBtn {
1010
1010
  display: flex;
1011
1011
  align-items: center;
@@ -3415,6 +3415,12 @@
3415
3415
  transform: scale(1.05);
3416
3416
  }
3417
3417
 
3418
+ .attachmentActionBtnActive {
3419
+ color: var(--brand-primary);
3420
+ border-color: color-mix(in srgb, var(--brand-primary) 65%, white);
3421
+ background: color-mix(in srgb, var(--brand-primary) 32%, rgba(0, 0, 0, 0.82));
3422
+ }
3423
+
3418
3424
  .attachmentCaptionInput,
3419
3425
  .documentAttachmentCaptionInput {
3420
3426
  min-width: 0;
@@ -3495,6 +3501,15 @@
3495
3501
  top: calc(100% + 2px);
3496
3502
  }
3497
3503
 
3504
+ .attachmentImageActionsMenu {
3505
+ position: fixed;
3506
+ z-index: calc(var(--z-overlay) + 1);
3507
+ top: auto;
3508
+ right: auto;
3509
+ width: 150px;
3510
+ max-width: calc(100vw - 16px);
3511
+ }
3512
+
3498
3513
  .contextMenu .contextDestructiveMenuItem {
3499
3514
  color: var(--color-error, #ef4444);
3500
3515
  }
@@ -3652,50 +3667,6 @@
3652
3667
  color: var(--text-primary, #fff);
3653
3668
  }
3654
3669
 
3655
- .documentAttachmentType {
3656
- flex-shrink: 0;
3657
- position: relative;
3658
- width: 28px;
3659
- height: 28px;
3660
- display: inline-flex;
3661
- align-items: center;
3662
- justify-content: center;
3663
- font-size: 9px;
3664
- line-height: 1;
3665
- font-weight: 700;
3666
- letter-spacing: 0.03em;
3667
- color: var(--text-muted, #94a3b8);
3668
- padding: 0;
3669
- }
3670
-
3671
- .documentAttachmentTypeOutline {
3672
- position: absolute;
3673
- inset: 0;
3674
- width: 28px;
3675
- height: 28px;
3676
- overflow: visible;
3677
- fill: rgba(255, 255, 255, 0.04);
3678
- stroke: var(--border-primary);
3679
- stroke-width: 1;
3680
- stroke-linecap: round;
3681
- stroke-linejoin: round;
3682
- }
3683
-
3684
- .documentAttachmentTypeFoldFill {
3685
- fill: color-mix(in srgb, var(--border-primary) 35%, var(--bg-secondary));
3686
- stroke: none;
3687
- }
3688
-
3689
- .documentAttachmentTypeFoldLine {
3690
- fill: none;
3691
- }
3692
-
3693
- .documentAttachmentTypeLabel {
3694
- position: relative;
3695
- z-index: 1;
3696
- transform: translateY(2px);
3697
- }
3698
-
3699
3670
  .brokenImage {
3700
3671
  display: flex;
3701
3672
  align-items: center;
@@ -24,6 +24,17 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
24
24
  normalizedCurrentActorLabel !== 'user' &&
25
25
  normalizedCurrentActorLabel !== 'human' &&
26
26
  normalizedCurrentActorLabel !== normalizedCurrentActorId);
27
+ const resolveCanonicalCurrentActorLabel = React.useCallback((presentation) => {
28
+ const normalizedLabel = String(presentation.label || '').trim().toLowerCase();
29
+ const isLegacyCurrentUserLabel = normalizedLabel === 'you'
30
+ || normalizedLabel === 'user'
31
+ || normalizedLabel === 'human';
32
+ return presentation.isCurrentActorHint === true
33
+ && hasCanonicalCurrentActorLabel
34
+ && isLegacyCurrentUserLabel
35
+ ? currentActorLabel
36
+ : presentation.label;
37
+ }, [currentActorLabel, hasCanonicalCurrentActorLabel]);
27
38
  return (_jsxs("div", { ref: listRef, className: [timelineStyles.thread, listClassName].filter(Boolean).join(' '), onScroll: onScroll, children: [items.length === 0 ? (_jsx("div", { className: [timelineStyles.empty, emptyClassName].filter(Boolean).join(' '), children: emptyMessage })) : null, items.map((item) => {
28
39
  const isTargetEntry = Boolean(normalizedTargetEntryId
29
40
  && (item.id === normalizedTargetEntryId
@@ -60,10 +71,8 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
60
71
  ? commentActorIds.includes(normalizedCurrentActorId)
61
72
  : actorPresentation.isCurrentActorHint === true
62
73
  : commentKind === 'member';
63
- const authorLabel = isCurrentActor &&
64
- actorPresentation.label.trim().toLowerCase() === 'you' &&
65
- hasCanonicalCurrentActorLabel
66
- ? currentActorLabel
74
+ const authorLabel = isCurrentActor
75
+ ? resolveCanonicalCurrentActorLabel(actorPresentation)
67
76
  : actorPresentation.label;
68
77
  const { ActorIcon } = actorPresentation;
69
78
  const authorColor = actorPresentation.kind === 'system'
@@ -96,6 +105,7 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
96
105
  actorProfile: item.event.actorProfile || null,
97
106
  fallbackKind: 'system',
98
107
  });
108
+ const authorLabel = resolveCanonicalCurrentActorLabel(actorPresentation);
99
109
  const { ActorIcon } = actorPresentation;
100
110
  const authorColor = actorPresentation.color;
101
111
  const changeEntries = Object.entries(item.event.details?.changes || {});
@@ -109,7 +119,7 @@ export function EntityActivityTimeline({ activity, comments, formatFieldValue, r
109
119
  timelineStyles.item,
110
120
  timelineStyles.event,
111
121
  actorPresentation.kind === 'system' ? timelineStyles.eventSystem : '',
112
- ].filter(Boolean).join(' '), children: _jsxs("div", { className: timelineStyles.eventMain, children: [_jsxs("span", { className: timelineStyles.author, style: authorColor ? { color: authorColor } : undefined, children: [_jsx(ActorIcon, { size: 12, strokeWidth: 1.5 }), actorPresentation.label] }), _jsxs("div", { className: timelineStyles.eventText, children: [_jsx("div", { children: summarizeEntityEvent(item.event, formatFieldValue) }), displayedChangeEntries.length > 0 ? (_jsx("div", { className: timelineStyles.eventChanges, children: displayedChangeEntries.map(([field, change]) => (_jsx("div", { className: timelineStyles.eventChange, children: summarizeTaskChange(field, change, formatFieldValue) }, field))) })) : null] }), _jsx("time", { className: timelineStyles.time, dateTime: item.event.createdAt, children: formatDate(item.event.createdAt, {
122
+ ].filter(Boolean).join(' '), children: _jsxs("div", { className: timelineStyles.eventMain, children: [_jsxs("span", { className: timelineStyles.author, style: authorColor ? { color: authorColor } : undefined, children: [_jsx(ActorIcon, { size: 12, strokeWidth: 1.5 }), authorLabel] }), _jsxs("div", { className: timelineStyles.eventText, children: [_jsx("div", { children: summarizeEntityEvent(item.event, formatFieldValue) }), displayedChangeEntries.length > 0 ? (_jsx("div", { className: timelineStyles.eventChanges, children: displayedChangeEntries.map(([field, change]) => (_jsx("div", { className: timelineStyles.eventChange, children: summarizeTaskChange(field, change, formatFieldValue) }, field))) })) : null] }), _jsx("time", { className: timelineStyles.time, dateTime: item.event.createdAt, children: formatDate(item.event.createdAt, {
113
123
  month: 'short',
114
124
  day: 'numeric',
115
125
  hour: '2-digit',
@@ -218,5 +218,5 @@ export function AttachmentPreviewHost({ theme }) {
218
218
  ? `Copied ${referenceLabel}`
219
219
  : `Copy ${referenceLabel}`, className: styles.referenceBadge }))] })] }), _jsx("button", { type: "button", className: "tf-control-icon", "aria-label": "Close preview", title: "Close preview", onClick: () => setPreview(null), children: _jsx(X, { size: 18 }) })] }), _jsxs("div", { className: styles.content, children: [isImage && _jsx("img", { className: styles.image, src: path, alt: attachmentName }), isPdf && (_jsx("iframe", { className: styles.pdf, src: path, title: `Preview ${attachmentName}` })), isText && textLoading && _jsx("div", { className: styles.message, children: "Loading preview\u2026" }), isText && textError && _jsx("div", { className: styles.message, children: textError }), isText && !textLoading && !textError && (isMarkdown
220
220
  ? _jsx("div", { className: styles.textPreview, children: _jsx(Markdown, { variant: "detail", children: textContent }) })
221
- : _jsx("pre", { className: styles.codePreview, children: textContent })), !isImage && !isPdf && !isText && (_jsxs("div", { className: styles.unavailable, children: [_jsx(FileText, { size: 34, "aria-hidden": "true" }), _jsx("strong", { children: "Preview unavailable" }), _jsx("span", { children: "This file type can be opened or downloaded instead." })] }))] }), _jsxs("footer", { className: styles.footer, children: [_jsxs("div", { className: styles.secondaryActions, children: [!isExternal && (_jsxs("a", { className: styles.secondaryLink, href: buildDownloadUrl(path, attachmentName), target: "_blank", rel: "noreferrer", children: [_jsx(Download, { size: 14 }), " Download"] })), (isExternal || (!canOpenImageNotes && !canOpenDocumentManager)) && (_jsxs("a", { className: styles.secondaryLink, href: path, target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { size: 14 }), " Open original"] }))] }), (canOpenImageNotes || canOpenDocumentManager) && (_jsxs("button", { type: "button", className: "tf-button-primary tf-button-compact", onClick: openManager, children: [canOpenImageNotes ? _jsx(PencilLine, { size: 14 }) : _jsx(ExternalLink, { size: 14 }), canOpenImageNotes ? 'Open in Image Notes' : 'Open in Documents'] }))] })] }) }), document.body);
221
+ : _jsx("pre", { className: styles.codePreview, children: textContent })), !isImage && !isPdf && !isText && (_jsxs("div", { className: styles.unavailable, children: [_jsx(FileText, { size: 34, "aria-hidden": "true" }), _jsx("strong", { children: "Preview unavailable" }), _jsx("span", { children: "This file type can be opened or downloaded instead." })] }))] }), _jsxs("footer", { className: styles.footer, children: [_jsxs("div", { className: styles.secondaryActions, children: [!isExternal && (_jsxs("a", { className: styles.secondaryLink, href: buildDownloadUrl(path, attachmentName), target: "_blank", rel: "noreferrer", children: [_jsx(Download, { size: 14 }), " Download"] })), (isExternal || (!canOpenImageNotes && !canOpenDocumentManager)) && (_jsxs("a", { className: styles.secondaryLink, href: path, target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { size: 14 }), " Open original"] }))] }), (canOpenImageNotes || canOpenDocumentManager) && (_jsxs("button", { type: "button", className: "tf-button-primary tf-button-compact", onClick: openManager, children: [canOpenImageNotes ? _jsx(PencilLine, { size: 14 }) : _jsx(ExternalLink, { size: 14 }), canOpenImageNotes ? 'Open in Image Notes' : 'Open'] }))] })] }) }), document.body);
222
222
  }
@@ -11,6 +11,9 @@ export interface ContextAttachmentManagerProps {
11
11
  onAddAttachment: (file: AttachmentItem) => void;
12
12
  onRemoveAttachment: (index: number) => void;
13
13
  onUpdateAttachmentCaption: (index: number, caption: string) => void;
14
+ cardCoverAssetId?: string | null;
15
+ onSetCardCover?: (assetId: string | null) => void | Promise<void>;
16
+ cardCoverDisabled?: boolean;
14
17
  readOnly?: boolean;
15
18
  defaultExpanded?: boolean;
16
19
  supplementalContent?: React.ReactNode;
@@ -1,15 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
- import { AlertTriangle, Check, Copy, Download, ExternalLink, Eye, FileUp, Link2, Loader2, MoreHorizontal, PencilLine, Plus, Trash2, X } from 'lucide-react';
3
+ import { createPortal } from 'react-dom';
4
+ import { AlertTriangle, Check, Copy, Download, ExternalLink, Eye, FileUp, Link2, Loader2, MoreHorizontal, PanelTop, PencilLine, Plus, Trash2, X } from 'lucide-react';
4
5
  import styles from '../../Taskforce.module.css';
5
6
  import { DisclosureSectionHeader } from '../ui/DisclosureSectionHeader';
6
- import { dispatchOpenMarkdownDocument, isMarkdownContextFile, resolveContextFsPath } from '../../utils/contextFiles';
7
+ import { dispatchOpenMarkdownDocument, isMarkdownContextFile, resolveContextAssetId, resolveContextFsPath } from '../../utils/contextFiles';
7
8
  import { canOpenAnnotatedAttachment, dispatchOpenAnnotatedAttachment } from '../../utils/annotatedAttachments';
8
9
  import { dispatchTaskforceContextAssetsMutated } from '../../utils/contextAssetEvents';
9
10
  import { getDocumentReferenceLabel } from '../../utils/documentReferences';
10
11
  import { uploadContextAttachment } from '../../utils/contextAttachmentUpload';
11
12
  import { dispatchPreviewContextAttachment } from '../../utils/contextAttachmentPreview';
12
13
  import { ReferenceBadgeButton } from '../ui/ReferenceBadgeButton';
14
+ import { DocumentTypeIcon } from '../ui/DocumentTypeIcon';
13
15
  const ACCEPTED_CONTEXT_FILE_TYPES = [
14
16
  '.png', '.jpg', '.jpeg', '.webp', '.gif',
15
17
  '.pdf', '.txt', '.md', '.csv', '.json', '.doc', '.docx',
@@ -75,7 +77,7 @@ function getAttachmentTypeLabel(item) {
75
77
  return /^https?:\/\//i.test(path) ? 'LINK' : 'FILE';
76
78
  }
77
79
  export function ContextAttachmentManager(props) {
78
- const { ownerType = 'task', ownerId, ownerReferenceLabel, workspaceId, attachments, onAddAttachment, onRemoveAttachment, onUpdateAttachmentCaption, readOnly = false, defaultExpanded = true, supplementalContent, } = props;
80
+ const { ownerType = 'task', ownerId, ownerReferenceLabel, workspaceId, attachments, onAddAttachment, onRemoveAttachment, onUpdateAttachmentCaption, cardCoverAssetId = null, onSetCardCover, cardCoverDisabled = false, readOnly = false, defaultExpanded = true, supplementalContent, } = props;
79
81
  const inputRef = useRef(null);
80
82
  const managerRef = useRef(null);
81
83
  const [uploading, setUploading] = useState(false);
@@ -83,10 +85,13 @@ export function ContextAttachmentManager(props) {
83
85
  const [linkValue, setLinkValue] = useState('');
84
86
  const [linkError, setLinkError] = useState(null);
85
87
  const [contextNotice, setContextNotice] = useState(null);
88
+ const [cardCoverBusyAssetId, setCardCoverBusyAssetId] = useState(null);
86
89
  const [showContextUpload, setShowContextUpload] = useState(defaultExpanded);
87
90
  const [showAddMenu, setShowAddMenu] = useState(false);
88
91
  const [showLinkComposer, setShowLinkComposer] = useState(false);
89
92
  const [openActionsIndex, setOpenActionsIndex] = useState(null);
93
+ const [openImageActionsIndex, setOpenImageActionsIndex] = useState(null);
94
+ const [imageActionsMenuPosition, setImageActionsMenuPosition] = useState({ top: 0, left: 0 });
90
95
  const [captionEditor, setCaptionEditor] = useState(null);
91
96
  const [copiedDocumentReference, setCopiedDocumentReference] = useState(null);
92
97
  const copyResetTimerRef = useRef(null);
@@ -94,6 +99,9 @@ export function ContextAttachmentManager(props) {
94
99
  if (copyResetTimerRef.current !== null)
95
100
  window.clearTimeout(copyResetTimerRef.current);
96
101
  }, []);
102
+ useEffect(() => {
103
+ setCardCoverBusyAssetId(null);
104
+ }, [ownerId, ownerType, workspaceId]);
97
105
  const copyDocumentReference = async (referenceLabel) => {
98
106
  try {
99
107
  await navigator.clipboard.writeText(referenceLabel);
@@ -121,6 +129,9 @@ export function ContextAttachmentManager(props) {
121
129
  const addFirstActionRef = useRef(null);
122
130
  const attachmentActionsTriggerRef = useRef(null);
123
131
  const attachmentFirstActionRef = useRef(null);
132
+ const imageActionsTriggerRef = useRef(null);
133
+ const imageActionsMenuRef = useRef(null);
134
+ const imageFirstActionRef = useRef(null);
124
135
  const captionFocusReturnRef = useRef(null);
125
136
  const linkInputRef = useRef(null);
126
137
  const contextKeyRef = useRef(new Set());
@@ -171,6 +182,7 @@ export function ContextAttachmentManager(props) {
171
182
  setShowAddMenu(false);
172
183
  setShowLinkComposer(false);
173
184
  setOpenActionsIndex(null);
185
+ setOpenImageActionsIndex(null);
174
186
  setCaptionEditor(null);
175
187
  setIsDropActive(false);
176
188
  setDragDepth(0);
@@ -217,7 +229,7 @@ export function ContextAttachmentManager(props) {
217
229
  return () => window.clearTimeout(timeoutId);
218
230
  }, [contextNotice]);
219
231
  useEffect(() => {
220
- if (!showAddMenu && openActionsIndex === null)
232
+ if (!showAddMenu && openActionsIndex === null && openImageActionsIndex === null)
221
233
  return;
222
234
  const handlePointerDown = (event) => {
223
235
  const target = event.target;
@@ -227,10 +239,15 @@ export function ContextAttachmentManager(props) {
227
239
  if (openActionsIndex !== null && !target?.closest('[data-context-actions-menu="true"]')) {
228
240
  setOpenActionsIndex(null);
229
241
  }
242
+ if (openImageActionsIndex !== null
243
+ && !imageActionsTriggerRef.current?.contains(target)
244
+ && !imageActionsMenuRef.current?.contains(target)) {
245
+ setOpenImageActionsIndex(null);
246
+ }
230
247
  };
231
248
  document.addEventListener('pointerdown', handlePointerDown);
232
249
  return () => document.removeEventListener('pointerdown', handlePointerDown);
233
- }, [openActionsIndex, showAddMenu]);
250
+ }, [openActionsIndex, openImageActionsIndex, showAddMenu]);
234
251
  useEffect(() => {
235
252
  if (!showLinkComposer)
236
253
  return;
@@ -246,12 +263,45 @@ export function ContextAttachmentManager(props) {
246
263
  return;
247
264
  attachmentFirstActionRef.current?.focus();
248
265
  }, [openActionsIndex]);
266
+ useEffect(() => {
267
+ if (openImageActionsIndex === null)
268
+ return;
269
+ imageFirstActionRef.current?.focus();
270
+ }, [openImageActionsIndex]);
271
+ useLayoutEffect(() => {
272
+ if (openImageActionsIndex === null)
273
+ return;
274
+ const positionMenu = () => {
275
+ const triggerRect = imageActionsTriggerRef.current?.getBoundingClientRect();
276
+ if (!triggerRect)
277
+ return;
278
+ const menuRect = imageActionsMenuRef.current?.getBoundingClientRect();
279
+ const menuWidth = menuRect?.width || 150;
280
+ const menuHeight = menuRect?.height || 108;
281
+ const gap = 4;
282
+ const viewportPadding = 8;
283
+ const left = Math.max(viewportPadding, Math.min(triggerRect.right - menuWidth, window.innerWidth - menuWidth - viewportPadding));
284
+ const preferredTop = triggerRect.bottom + gap;
285
+ const top = preferredTop + menuHeight <= window.innerHeight - viewportPadding
286
+ ? preferredTop
287
+ : Math.max(viewportPadding, triggerRect.top - menuHeight - gap);
288
+ setImageActionsMenuPosition({ top, left });
289
+ };
290
+ positionMenu();
291
+ window.addEventListener('resize', positionMenu);
292
+ window.addEventListener('scroll', positionMenu, true);
293
+ return () => {
294
+ window.removeEventListener('resize', positionMenu);
295
+ window.removeEventListener('scroll', positionMenu, true);
296
+ };
297
+ }, [openImageActionsIndex]);
249
298
  useEffect(() => {
250
299
  if (!readOnly)
251
300
  return;
252
301
  setShowAddMenu(false);
253
302
  setShowLinkComposer(false);
254
303
  setOpenActionsIndex(null);
304
+ setOpenImageActionsIndex(null);
255
305
  setCaptionEditor(null);
256
306
  setIsDropActive(false);
257
307
  setDragDepth(0);
@@ -572,6 +622,7 @@ export function ContextAttachmentManager(props) {
572
622
  setShowAddMenu(false);
573
623
  setShowLinkComposer(false);
574
624
  setOpenActionsIndex(null);
625
+ setOpenImageActionsIndex(null);
575
626
  setCaptionEditor(null);
576
627
  setIsDropActive(false);
577
628
  setDragDepth(0);
@@ -623,6 +674,16 @@ export function ContextAttachmentManager(props) {
623
674
  const isExternalUrl = /^https?:\/\//i.test(path);
624
675
  const canAnnotate = ownerType === 'task'
625
676
  && canOpenAnnotatedAttachment(item, { taskId: trimmedOwnerId });
677
+ const assetId = typeof item === 'string' ? '' : String(item.assetId || '').trim();
678
+ const isCardCover = Boolean(assetId && assetId === cardCoverAssetId);
679
+ const canSetCardCover = ownerType === 'task'
680
+ && Boolean(trimmedOwnerId)
681
+ && Boolean(assetId)
682
+ && typeof item !== 'string'
683
+ && item.linkRole === 'image'
684
+ && Boolean(onSetCardCover)
685
+ && !readOnly;
686
+ const hasImageActionMenu = Boolean(fsPath || !isExternalUrl || !readOnly);
626
687
  return (_jsxs("div", { className: styles.attachmentCard, children: [_jsxs("div", { className: styles.attachmentPreviewContainer, children: [isImage ? (_jsx("button", { type: "button", className: styles.attachmentPreviewButton, "aria-label": `Open ${caption || displayName}`, onClick: () => {
627
688
  if (dispatchPreviewContextAttachment(item, {
628
689
  ownerType,
@@ -650,22 +711,56 @@ export function ContextAttachmentManager(props) {
650
711
  if (dispatchOpenMarkdownDocument(markdownTarget, fsPath))
651
712
  return;
652
713
  window.open(path, '_blank');
653
- }, title: "Open file", children: [_jsx(FileUp, { size: 16 }), _jsx("span", { children: caption || fsPath || path.split('/').pop() || 'Context file' })] })), _jsxs("div", { className: styles.brokenImagePlaceholder, children: [_jsx(AlertTriangle, { size: 16 }), _jsx("span", { children: "Preview unavailable" })] }), _jsxs("div", { className: styles.attachmentActions, children: [canAnnotate && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
714
+ }, title: "Open file", children: [_jsx(FileUp, { size: 16 }), _jsx("span", { children: caption || fsPath || path.split('/').pop() || 'Context file' })] })), _jsxs("div", { className: styles.brokenImagePlaceholder, children: [_jsx(AlertTriangle, { size: 16 }), _jsx("span", { children: "Preview unavailable" })] }), _jsxs("div", { className: styles.attachmentActions, children: [canSetCardCover && (_jsx("button", { type: "button", className: `${styles.attachmentActionBtn} ${isCardCover ? styles.attachmentActionBtnActive : ''}`, "aria-pressed": isCardCover, "aria-label": isCardCover ? 'Remove from task card' : 'Show on task card', onClick: (event) => {
715
+ event.stopPropagation();
716
+ setCardCoverBusyAssetId(assetId);
717
+ setUploadError(null);
718
+ void Promise.resolve(onSetCardCover?.(isCardCover ? null : assetId))
719
+ .then(() => {
720
+ setContextNotice(isCardCover
721
+ ? 'Removed image from task card.'
722
+ : 'Image shown on task card.');
723
+ })
724
+ .catch((error) => {
725
+ setUploadError(error instanceof Error
726
+ ? error.message
727
+ : 'Unable to update the task card image.');
728
+ })
729
+ .finally(() => setCardCoverBusyAssetId(null));
730
+ }, disabled: cardCoverDisabled || cardCoverBusyAssetId !== null, title: cardCoverDisabled
731
+ ? 'Attachment is still saving'
732
+ : isCardCover ? 'Remove from task card' : 'Show on task card', children: cardCoverBusyAssetId === assetId
733
+ ? _jsx(Loader2, { size: 12, className: styles.spinner })
734
+ : _jsx(PanelTop, { size: 12 }) })), canAnnotate && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, "aria-label": "Annotate image", onClick: (e) => {
654
735
  e.stopPropagation();
655
736
  dispatchOpenAnnotatedAttachment(item, {
656
737
  taskId: trimmedOwnerId,
657
738
  taskReferenceLabel: ownerReferenceLabel,
658
739
  });
659
- }, title: "Annotate", children: _jsx(PencilLine, { size: 12 }) })), fsPath && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
660
- e.stopPropagation();
661
- navigator.clipboard.writeText(fsPath);
662
- }, title: `Copy path: ${fsPath}`, children: _jsx(Copy, { size: 12 }) })), !isExternalUrl && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
663
- e.stopPropagation();
664
- window.open(buildDownloadUrl(path, caption || displayName), '_blank');
665
- }, title: "Download", children: _jsx(Download, { size: 12 }) })), !readOnly && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
666
- e.stopPropagation();
667
- onRemoveAttachment(idx);
668
- }, title: "Delete", children: _jsx(Trash2, { size: 12 }) }))] })] }), captionEditor?.index === idx ? (_jsxs("div", { className: styles.contextCaptionEditor, onBlur: (event) => {
740
+ }, title: "Annotate", children: _jsx(PencilLine, { size: 12 }) })), hasImageActionMenu && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, "aria-label": `Actions for ${displayName}`, "aria-expanded": openImageActionsIndex === idx, onClick: (event) => {
741
+ event.stopPropagation();
742
+ imageActionsTriggerRef.current = event.currentTarget;
743
+ setOpenActionsIndex(null);
744
+ setOpenImageActionsIndex(openImageActionsIndex === idx ? null : idx);
745
+ }, title: "More actions", children: _jsx(MoreHorizontal, { size: 12 }) })), openImageActionsIndex === idx && createPortal(_jsxs("div", { ref: imageActionsMenuRef, className: `${styles.contextMenu} ${styles.attachmentImageActionsMenu}`, role: "group", "aria-label": `Actions for ${displayName}`, style: imageActionsMenuPosition, onClick: (event) => event.stopPropagation(), onKeyDown: (event) => {
746
+ if (event.key !== 'Escape')
747
+ return;
748
+ event.preventDefault();
749
+ event.stopPropagation();
750
+ setOpenImageActionsIndex(null);
751
+ imageActionsTriggerRef.current?.focus();
752
+ }, children: [fsPath && (_jsxs("button", { ref: imageFirstActionRef, type: "button", onClick: () => {
753
+ void navigator.clipboard.writeText(fsPath);
754
+ setOpenImageActionsIndex(null);
755
+ window.requestAnimationFrame(() => imageActionsTriggerRef.current?.focus());
756
+ }, children: [_jsx(Copy, { size: 14 }), " Copy path"] })), !isExternalUrl && (_jsxs("button", { ref: fsPath ? undefined : imageFirstActionRef, type: "button", onClick: () => {
757
+ window.open(buildDownloadUrl(path, caption || displayName), '_blank');
758
+ setOpenImageActionsIndex(null);
759
+ window.requestAnimationFrame(() => imageActionsTriggerRef.current?.focus());
760
+ }, children: [_jsx(Download, { size: 14 }), " Download"] })), !readOnly && (_jsxs("button", { ref: !fsPath && isExternalUrl ? imageFirstActionRef : undefined, type: "button", className: styles.contextDestructiveMenuItem, onClick: () => {
761
+ onRemoveAttachment(idx);
762
+ setOpenImageActionsIndex(null);
763
+ }, children: [_jsx(Trash2, { size: 14 }), " Delete"] }))] }), document.body)] })] }), captionEditor?.index === idx ? (_jsxs("div", { className: styles.contextCaptionEditor, onBlur: (event) => {
669
764
  if (!event.currentTarget.contains(event.relatedTarget)) {
670
765
  commitCaptionEdit(false);
671
766
  }
@@ -700,8 +795,9 @@ export function ContextAttachmentManager(props) {
700
795
  : { ...item, taskId: trimmedOwnerId })
701
796
  : item;
702
797
  const canOpenInDocumentManager = isMarkdownContextFile(markdownTarget, fsPath)
703
- && Boolean(resolveContextFsPath(markdownTarget, fsPath));
704
- return (_jsxs("div", { className: styles.documentAttachmentItem, children: [_jsxs("div", { className: styles.documentAttachmentRow, children: [_jsxs("div", { className: styles.documentAttachmentMain, children: [documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedDocumentReference === documentReferenceLabel, title: `Copy document reference ${documentReferenceLabel}`, ariaLabel: `Copy document reference ${documentReferenceLabel}`, className: styles.documentAttachmentReference, onClick: () => { void copyDocumentReference(documentReferenceLabel); } })), _jsx("button", { type: "button", className: styles.documentAttachmentLink, onClick: () => {
798
+ && Boolean(resolveContextFsPath(markdownTarget, fsPath)
799
+ || resolveContextAssetId(markdownTarget));
800
+ return (_jsxs("div", { className: styles.documentAttachmentItem, children: [_jsxs("div", { className: styles.documentAttachmentRow, children: [_jsxs("div", { className: styles.documentAttachmentMain, children: [_jsx(DocumentTypeIcon, { label: fileType }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedDocumentReference === documentReferenceLabel, title: `Copy document reference ${documentReferenceLabel}`, ariaLabel: `Copy document reference ${documentReferenceLabel}`, className: styles.documentAttachmentReference, onClick: () => { void copyDocumentReference(documentReferenceLabel); } })), _jsx("button", { type: "button", className: styles.documentAttachmentLink, onClick: () => {
705
801
  if (dispatchPreviewContextAttachment(item, {
706
802
  ownerType,
707
803
  ownerId: trimmedOwnerId || null,
@@ -712,7 +808,7 @@ export function ContextAttachmentManager(props) {
712
808
  if (dispatchOpenMarkdownDocument(markdownTarget, fsPath))
713
809
  return;
714
810
  window.open(path, '_blank');
715
- }, title: displayName, "aria-label": `${caption || displayName} ${fileType}`, children: _jsx("span", { className: styles.documentAttachmentName, children: caption || displayName }) }), _jsxs("span", { className: styles.documentAttachmentType, children: [_jsxs("svg", { className: styles.documentAttachmentTypeOutline, viewBox: "0 0 28 28", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { d: "M2 0.75h18.25l7 7V26q0 1.25-1.25 1.25H2Q0.75 27.25 0.75 26V2Q0.75 0.75 2 0.75Z" }), _jsx("path", { className: styles.documentAttachmentTypeFoldFill, d: "M20.25 0.75v7h7Z" }), _jsx("path", { className: styles.documentAttachmentTypeFoldLine, d: "M20.25 0.75v7h7" })] }), _jsx("span", { className: styles.documentAttachmentTypeLabel, children: fileType })] })] }), (!readOnly || Boolean(fsPath) || !isExternalUrl) && (_jsxs("div", { className: styles.contextMenuControl, "data-context-actions-menu": "true", onKeyDown: (event) => {
811
+ }, title: displayName, "aria-label": `${caption || displayName} ${fileType}`, children: _jsx("span", { className: styles.documentAttachmentName, children: caption || displayName }) })] }), (!readOnly || Boolean(fsPath) || !isExternalUrl) && (_jsxs("div", { className: styles.contextMenuControl, "data-context-actions-menu": "true", onKeyDown: (event) => {
716
812
  if (event.key !== 'Escape' || openActionsIndex !== idx)
717
813
  return;
718
814
  event.preventDefault();
@@ -737,7 +833,7 @@ export function ContextAttachmentManager(props) {
737
833
  window.open(path, '_blank');
738
834
  }
739
835
  setOpenActionsIndex(null);
740
- }, children: [_jsx(ExternalLink, { size: 14 }), " Open in Documents"] })), !readOnly && (_jsxs("button", { type: "button", onClick: () => {
836
+ }, children: [_jsx(ExternalLink, { size: 14 }), " Open"] })), !readOnly && (_jsxs("button", { type: "button", onClick: () => {
741
837
  captionFocusReturnRef.current = attachmentActionsTriggerRef.current;
742
838
  startCaptionEdit(idx, caption);
743
839
  setOpenActionsIndex(null);
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { TaskAssigneeOption } from '../../../utils/assignees';
3
3
  import type { PlanningNodeSummary, PlanningStatusCounts } from './planningWorkspaceModel';
4
+ import { type PlanningIdentityPatch } from './PlanningVisualIdentity';
4
5
  export declare function PlanningReferenceBadge({ label, entityType, entityName, interactive, provisional, pending, }: {
5
6
  label: string;
6
7
  entityType: 'initiative' | 'workstream' | 'task';
@@ -34,11 +35,12 @@ export declare function PlanningSummaryMeta({ item, workstreamCount, className,
34
35
  workstreamCount?: number;
35
36
  className?: string;
36
37
  }): import("react/jsx-runtime").JSX.Element;
37
- export declare function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, }: {
38
+ export declare function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, onChangeIdentity, }: {
38
39
  item: PlanningNodeSummary;
39
40
  entityType: 'initiative' | 'workstream';
40
41
  workstreamCount?: number;
41
42
  ownerOption?: TaskAssigneeOption;
42
43
  ownerOptions?: TaskAssigneeOption[];
43
44
  onChangeOwner?: (ownerId: string) => void | Promise<void>;
45
+ onChangeIdentity?: (patch: PlanningIdentityPatch) => void | Promise<void>;
44
46
  }): import("react/jsx-runtime").JSX.Element;
@@ -6,6 +6,7 @@ import { getStatusDefinition, resolveColor } from '../../../utils/constants';
6
6
  import { ReferenceBadgeButton } from '../../ui/ReferenceBadgeButton';
7
7
  import { PlanningCardIdentity } from './PlanningCardIdentity';
8
8
  import styles from './PlanningEntitySummary.module.css';
9
+ import { PlanningVisualIdentity } from './PlanningVisualIdentity';
9
10
  const PLANNING_PROGRESS_STATUS_ORDER = [
10
11
  'done',
11
12
  'review',
@@ -91,7 +92,7 @@ export function PlanningSummaryMeta({ item, workstreamCount, className = '', })
91
92
  const normalizedWorkstreamCount = Math.max(0, Number(workstreamCount) || 0);
92
93
  return (_jsxs(PlanningCardMeta, { className: className, children: [workstreamCount !== undefined ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.scopeCount, children: [normalizedWorkstreamCount, " ", normalizedWorkstreamCount === 1 ? 'workstream' : 'workstreams'] }), _jsx("span", { "aria-hidden": "true", children: "\u2022" })] })) : null, hasTasks ? (_jsxs(_Fragment, { children: [_jsxs("span", { children: [item.completedTaskCount, " of ", item.taskCount, " complete"] }), _jsx("span", { "aria-hidden": "true", children: "\u2022" })] })) : null, _jsx(PlanningContextCount, { count: item.attachmentCount })] }));
93
94
  }
94
- export function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, }) {
95
+ export function PlanningEntitySummary({ item, entityType, workstreamCount, ownerOption, ownerOptions, onChangeOwner, onChangeIdentity, }) {
95
96
  const hasTasks = item.taskCount > 0;
96
- return (_jsxs(_Fragment, { children: [_jsx(PlanningCardTitle, { children: item.title }), hasTasks ? (_jsx(PlanningProgress, { value: item.progressPercent, label: `${item.title}: ${item.progressPercent}% resolved`, statusCounts: item.statusCounts })) : null, _jsxs(PlanningCardFooter, { identity: (_jsx(PlanningCardIdentity, { role: "owner", option: ownerOption, label: item.ownerLabel, value: item.ownerId, options: ownerOptions, onChange: onChangeOwner, disabled: Boolean(item.isArchived) })), children: [!hasTasks ? _jsx(PlanningNoTasks, {}) : null, _jsx(PlanningSummaryMeta, { item: item, workstreamCount: entityType === 'initiative' ? workstreamCount : undefined })] })] }));
97
+ return (_jsxs("div", { className: styles.summaryLayout, children: [_jsx(PlanningVisualIdentity, { entityType: entityType, entityId: item.id, icon: item.icon, color: item.color, disabled: Boolean(item.isArchived), onChange: onChangeIdentity }), _jsxs("div", { className: styles.summaryContent, children: [_jsx(PlanningCardTitle, { children: item.title }), hasTasks ? (_jsx(PlanningProgress, { value: item.progressPercent, label: `${item.title}: ${item.progressPercent}% resolved`, statusCounts: item.statusCounts })) : null, _jsxs(PlanningCardFooter, { identity: (_jsx(PlanningCardIdentity, { role: "owner", option: ownerOption, label: item.ownerLabel, value: item.ownerId, options: ownerOptions, onChange: onChangeOwner, disabled: Boolean(item.isArchived) })), children: [!hasTasks ? _jsx(PlanningNoTasks, {}) : null, _jsx(PlanningSummaryMeta, { item: item, workstreamCount: entityType === 'initiative' ? workstreamCount : undefined })] })] })] }));
97
98
  }
@@ -1,9 +1,11 @@
1
1
  import type { PlanningLinkOption } from './planningWorkspaceModel';
2
- export declare function PlanningLinkPicker({ options, selectedId, entityLabel, onSelect, disabled, className, }: {
2
+ export declare function PlanningLinkPicker({ options, selectedId, entityLabel, onSelect, disabled, className, iconTrigger, triggerTitle, }: {
3
3
  options: PlanningLinkOption[];
4
4
  selectedId?: string;
5
5
  entityLabel: 'initiative' | 'workstream' | 'task';
6
6
  onSelect: (option: PlanningLinkOption) => void | Promise<void>;
7
7
  disabled?: boolean;
8
8
  className?: string;
9
+ iconTrigger?: boolean;
10
+ triggerTitle?: string;
9
11
  }): import("react/jsx-runtime").JSX.Element;
@@ -3,9 +3,11 @@ import React from 'react';
3
3
  import { Link2 } from 'lucide-react';
4
4
  import { TaxonomyDropdown } from '../../ui/TaxonomyDropdown';
5
5
  import { PlanningReferenceBadge } from './PlanningEntitySummary';
6
+ import taskforceStyles from '../../../Taskforce.module.css';
6
7
  import controlStyles from './PlanningDropdownControl.module.css';
7
8
  import styles from './PlanningLinkPicker.module.css';
8
- export function PlanningLinkPicker({ options, selectedId = '', entityLabel, onSelect, disabled = false, className = '', }) {
9
+ import { PlanningVisualIdentity } from './PlanningVisualIdentity';
10
+ export function PlanningLinkPicker({ options, selectedId = '', entityLabel, onSelect, disabled = false, className = '', iconTrigger = false, triggerTitle, }) {
9
11
  const optionById = React.useMemo(() => new Map(options.map((option) => [option.id, option])), [options]);
10
12
  const dropdownOptions = React.useMemo(() => options.map((option) => ({
11
13
  value: option.id,
@@ -17,10 +19,10 @@ export function PlanningLinkPicker({ options, selectedId = '', entityLabel, onSe
17
19
  const option = optionById.get(String(value));
18
20
  if (option)
19
21
  void onSelect(option);
20
- }, disabled: disabled || options.length === 0, className: `${controlStyles.control} ${className}`.trim(), portalPanel: true, panelAlign: "start", panelMinWidth: 320, searchable: true, searchPlaceholder: `Search ${entityLabel}s by title or reference`, noResultsText: `No matching ${entityLabel}s.`, ariaLabel: linkLabel, triggerContent: selectedOption ? (_jsxs("span", { className: styles.triggerSelection, children: [_jsx(PlanningReferenceBadge, { label: selectedOption.referenceLabel, entityType: entityLabel, entityName: selectedOption.title }), _jsx("span", { children: selectedOption.title })] })) : (_jsxs("span", { className: styles.triggerAction, children: [_jsx(Link2, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: linkLabel })] })), renderOptionContent: (option) => {
22
+ }, disabled: disabled || options.length === 0, className: `${iconTrigger ? `${styles.iconPicker} ${taskforceStyles.taskWorkstreamPicker}` : controlStyles.control} ${className}`.trim(), portalPanel: true, panelAlign: "start", panelMinWidth: 320, searchable: true, searchPlaceholder: `Search ${entityLabel}s by title or reference`, noResultsText: `No matching ${entityLabel}s.`, ariaLabel: linkLabel, title: iconTrigger ? (triggerTitle || linkLabel) : undefined, hideChevron: iconTrigger, triggerContent: iconTrigger ? (_jsx(Link2, { size: 14, "aria-hidden": "true" })) : selectedOption ? (_jsxs("span", { className: styles.triggerSelection, children: [_jsx(PlanningReferenceBadge, { label: selectedOption.referenceLabel, entityType: entityLabel, entityName: selectedOption.title }), _jsx("span", { children: selectedOption.title })] })) : (_jsxs("span", { className: styles.triggerAction, children: [_jsx(Link2, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: linkLabel })] })), renderOptionContent: (option) => {
21
23
  const candidate = optionById.get(String(option.value));
22
24
  if (!candidate)
23
25
  return option.label;
24
- return (_jsxs("span", { className: styles.option, children: [_jsxs("span", { className: styles.optionTopRow, children: [_jsx(PlanningReferenceBadge, { label: candidate.referenceLabel, entityType: entityLabel, entityName: candidate.title }), _jsx("span", { className: styles.optionTitle, children: candidate.title })] }), candidate.meta ? _jsx("span", { className: styles.optionMeta, children: candidate.meta }) : null] }));
26
+ return (_jsxs("span", { className: styles.option, children: [_jsxs("span", { className: styles.optionTopRow, children: [entityLabel !== 'task' ? (_jsx(PlanningVisualIdentity, { entityType: entityLabel, entityId: candidate.id, icon: candidate.icon, color: candidate.color, size: "inline", disabled: true })) : null, _jsx(PlanningReferenceBadge, { label: candidate.referenceLabel, entityType: entityLabel, entityName: candidate.title }), _jsx("span", { className: styles.optionTitle, children: candidate.title })] }), candidate.meta ? _jsx("span", { className: styles.optionMeta, children: candidate.meta }) : null] }));
25
27
  }, placeholder: linkLabel }));
26
28
  }