@taskforcehq/taskforce 0.3.309 → 0.3.313

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 (470) hide show
  1. package/README.md +89 -127
  2. package/dist/TaskforceCore.js +1 -1
  3. package/dist/components/context/ContextAttachmentManager.js +4 -3
  4. package/dist/components/features/InitiativesModule.js +1 -1
  5. package/dist/components/features/TaskSettings.js +1 -1
  6. package/dist/components/ui/DisclosureSectionHeader.d.ts +12 -0
  7. package/dist/components/ui/DisclosureSectionHeader.js +8 -0
  8. package/dist/components/views/PlanComparisonPage.js +1 -1
  9. package/dist/components/views/StandaloneLayout.js +18 -4
  10. package/dist/components/views/panels/PlanningDrawer.js +2 -1
  11. package/dist/components/views/standalone/modals/AccountHubModal.js +1 -1
  12. package/dist/components/views/standalone/modals/HelpModal.d.ts +3 -1
  13. package/dist/components/views/standalone/modals/HelpModal.js +17 -2
  14. package/dist/core/PlanEntitlementService.d.ts +2 -0
  15. package/dist/core/PlanEntitlementService.js +57 -4
  16. package/dist/hooks/useFilterSort.js +1 -1
  17. package/dist/hooks/useTaskforce.js +2 -2
  18. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +5 -2
  19. package/dist/localization/locales/en-US.js +1 -1
  20. package/dist/localization/locales/es-419.js +1 -1
  21. package/dist/localization/locales/pt-BR.js +1 -1
  22. package/dist/mcp/runtime.js +49 -14
  23. package/dist/server/routes/billing.js +0 -1
  24. package/dist/ui/assets/AgentsModule-BULqIo8e.js +1 -0
  25. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CCckzWYZ.js → AnnotatedAttachmentWorkspace-DHr4rHK9.js} +1 -1
  26. package/dist/ui/assets/ContextAttachmentManager-DoMe2OpY.js +3 -0
  27. package/dist/ui/assets/{DocumentWorkspace-BVaWPA25.js → DocumentWorkspace-DumBWhvb.js} +1 -1
  28. package/dist/ui/assets/{EntityActivityTimeline-DSj0ThaK.js → EntityActivityTimeline-sHk8Nzbq.js} +1 -1
  29. package/dist/ui/assets/{InitiativesModule-AxMkWtxH.js → InitiativesModule-3X9UNzHh.js} +1 -1
  30. package/dist/ui/assets/PlansPage-C3A3EYaV.js +1 -0
  31. package/dist/ui/assets/PlansPage-DeAzWYTb.css +1 -0
  32. package/dist/ui/assets/{TaskContextUpload-Dw4rTF4i.js → TaskContextUpload-C8G7pWkX.js} +1 -1
  33. package/dist/ui/assets/{TaskSettings-f2ga42_V.js → TaskSettings-DM1o9Wla.js} +2 -2
  34. package/dist/ui/assets/{WorkflowsModule-E4UL3mov.js → WorkflowsModule-CW0crzvD.js} +1 -1
  35. package/dist/ui/assets/documentReferences-BZB3nMRb.js +1 -0
  36. package/dist/ui/assets/index-BWFQ9mGn.js +5 -0
  37. package/dist/ui/assets/{index-BvAbqx5Q.css → index-DcK_8tuE.css} +1 -1
  38. package/dist/ui/assets/{vendor-icons-B32hGuF2.js → vendor-icons-CLnehDTw.js} +1 -1
  39. package/dist/ui/index.html +3 -3
  40. package/dist/utils/supportContact.d.ts +9 -0
  41. package/dist/utils/supportContact.js +32 -0
  42. package/package.json +12 -4
  43. package/scripts/postinstall.js +4 -3
  44. package/dist/TaskforceCore.CategoryFallback.test.d.ts +0 -1
  45. package/dist/TaskforceCore.CategoryFallback.test.js +0 -101
  46. package/dist/TaskforceCore.filter-persistence.test.d.ts +0 -1
  47. package/dist/TaskforceCore.filter-persistence.test.js +0 -402
  48. package/dist/TaskforceCore.routing.test.d.ts +0 -1
  49. package/dist/TaskforceCore.routing.test.js +0 -311
  50. package/dist/TaskforceCore.test.d.ts +0 -1
  51. package/dist/TaskforceCore.test.js +0 -7910
  52. package/dist/TaskforceWidget.test.d.ts +0 -1
  53. package/dist/TaskforceWidget.test.js +0 -92
  54. package/dist/UnsavedChangesRelaxed.test.d.ts +0 -1
  55. package/dist/UnsavedChangesRelaxed.test.js +0 -16
  56. package/dist/__tests__/localizationCatalogs.test.d.ts +0 -1
  57. package/dist/__tests__/localizationCatalogs.test.js +0 -54
  58. package/dist/__tests__/workflowSimulation.test.d.ts +0 -1
  59. package/dist/__tests__/workflowSimulation.test.js +0 -113
  60. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts +0 -1
  61. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +0 -278
  62. package/dist/annotatedAttachments/payload.test.d.ts +0 -1
  63. package/dist/annotatedAttachments/payload.test.js +0 -67
  64. package/dist/compat/workspaceSyncCompat.test.d.ts +0 -1
  65. package/dist/compat/workspaceSyncCompat.test.js +0 -124
  66. package/dist/components/features/AgentsModule.test.d.ts +0 -1
  67. package/dist/components/features/AgentsModule.test.js +0 -601
  68. package/dist/components/features/AnnotatedAttachmentWorkspace.test.d.ts +0 -1
  69. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +0 -2883
  70. package/dist/components/features/DocumentWorkspace.test.d.ts +0 -1
  71. package/dist/components/features/DocumentWorkspace.test.js +0 -1439
  72. package/dist/components/features/TaskSettings.test.d.ts +0 -1
  73. package/dist/components/features/TaskSettings.test.js +0 -1674
  74. package/dist/components/features/settings/TaxonomyLibraryManager.test.d.ts +0 -1
  75. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +0 -141
  76. package/dist/components/task/TaskActionHeader.test.d.ts +0 -1
  77. package/dist/components/task/TaskActionHeader.test.js +0 -91
  78. package/dist/components/task/TaskCard.test.d.ts +0 -1
  79. package/dist/components/task/TaskCard.test.js +0 -513
  80. package/dist/components/task/TaskContextUpload.test.d.ts +0 -1
  81. package/dist/components/task/TaskContextUpload.test.js +0 -506
  82. package/dist/components/task/TaskForm.test.d.ts +0 -1
  83. package/dist/components/task/TaskForm.test.js +0 -946
  84. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +0 -1
  85. package/dist/components/task/TaskKanban.empty-column-drop.test.js +0 -66
  86. package/dist/components/task/TaskKanban.planning-drop.test.d.ts +0 -1
  87. package/dist/components/task/TaskKanban.planning-drop.test.js +0 -93
  88. package/dist/components/task/TaskKanban.test.d.ts +0 -1
  89. package/dist/components/task/TaskKanban.test.js +0 -99
  90. package/dist/components/task/TaskList.test.d.ts +0 -1
  91. package/dist/components/task/TaskList.test.js +0 -473
  92. package/dist/components/task/TaskStatusActions.test.d.ts +0 -1
  93. package/dist/components/task/TaskStatusActions.test.js +0 -54
  94. package/dist/components/task/taskKanbanDropRules.test.d.ts +0 -1
  95. package/dist/components/task/taskKanbanDropRules.test.js +0 -114
  96. package/dist/components/ui/LevelSelector.test.d.ts +0 -1
  97. package/dist/components/ui/LevelSelector.test.js +0 -55
  98. package/dist/components/ui/Markdown.test.d.ts +0 -1
  99. package/dist/components/ui/Markdown.test.js +0 -38
  100. package/dist/components/ui/MultiSelectFilter.test.d.ts +0 -1
  101. package/dist/components/ui/MultiSelectFilter.test.js +0 -74
  102. package/dist/components/ui/TaxonomyDropdown.test.d.ts +0 -1
  103. package/dist/components/ui/TaxonomyDropdown.test.js +0 -130
  104. package/dist/components/ui/TopNoticeLayer.test.d.ts +0 -1
  105. package/dist/components/ui/TopNoticeLayer.test.js +0 -25
  106. package/dist/components/views/AppShellHeader.test.d.ts +0 -1
  107. package/dist/components/views/AppShellHeader.test.js +0 -42
  108. package/dist/components/views/PlanComparisonPage.test.d.ts +0 -1
  109. package/dist/components/views/PlanComparisonPage.test.js +0 -757
  110. package/dist/components/views/PlansPage.test.d.ts +0 -1
  111. package/dist/components/views/PlansPage.test.js +0 -710
  112. package/dist/components/views/chrome/WorkspaceChromeSections.test.d.ts +0 -1
  113. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +0 -54
  114. package/dist/components/views/chrome/workspaceChrome.test.d.ts +0 -1
  115. package/dist/components/views/chrome/workspaceChrome.test.js +0 -52
  116. package/dist/components/views/panels/DocumentFilterToolbar.test.d.ts +0 -1
  117. package/dist/components/views/panels/DocumentFilterToolbar.test.js +0 -19
  118. package/dist/components/views/panels/FilterToolbar.test.d.ts +0 -1
  119. package/dist/components/views/panels/FilterToolbar.test.js +0 -47
  120. package/dist/components/views/panels/PlanningDrawer.test.d.ts +0 -1
  121. package/dist/components/views/panels/PlanningDrawer.test.js +0 -529
  122. package/dist/components/views/planningScope.test.d.ts +0 -1
  123. package/dist/components/views/planningScope.test.js +0 -60
  124. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +0 -1
  125. package/dist/components/views/standalone/modals/AccountHubModal.test.js +0 -66
  126. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +0 -1
  127. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +0 -24
  128. package/dist/components/views/teamManagementAccess.test.d.ts +0 -1
  129. package/dist/components/views/teamManagementAccess.test.js +0 -44
  130. package/dist/config/deployment.test.d.ts +0 -1
  131. package/dist/config/deployment.test.js +0 -90
  132. package/dist/config/envSchema.test.d.ts +0 -1
  133. package/dist/config/envSchema.test.js +0 -125
  134. package/dist/core/AiProfileService.test.d.ts +0 -1
  135. package/dist/core/AiProfileService.test.js +0 -184
  136. package/dist/core/AiProfiles.test.d.ts +0 -1
  137. package/dist/core/AiProfiles.test.js +0 -1873
  138. package/dist/core/AuthTokenService.test.d.ts +0 -1
  139. package/dist/core/AuthTokenService.test.js +0 -78
  140. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +0 -1
  141. package/dist/core/BulkUpdateFieldsPreview.test.js +0 -62
  142. package/dist/core/CompletedAt.test.d.ts +0 -1
  143. package/dist/core/CompletedAt.test.js +0 -44
  144. package/dist/core/CreatedBy.test.d.ts +0 -1
  145. package/dist/core/CreatedBy.test.js +0 -188
  146. package/dist/core/DeletedTaskTrash.test.d.ts +0 -1
  147. package/dist/core/DeletedTaskTrash.test.js +0 -104
  148. package/dist/core/EntitlementsPolicy.test.d.ts +0 -1
  149. package/dist/core/EntitlementsPolicy.test.js +0 -280
  150. package/dist/core/GlobalSettingsService.test.d.ts +0 -1
  151. package/dist/core/GlobalSettingsService.test.js +0 -45
  152. package/dist/core/JsonBackupRuntime.test.d.ts +0 -1
  153. package/dist/core/JsonBackupRuntime.test.js +0 -155
  154. package/dist/core/PlanFeatureCatalog.test.d.ts +0 -1
  155. package/dist/core/PlanFeatureCatalog.test.js +0 -139
  156. package/dist/core/PlanVersionPolicy.test.d.ts +0 -1
  157. package/dist/core/PlanVersionPolicy.test.js +0 -204
  158. package/dist/core/PlanningEntities.test.d.ts +0 -1
  159. package/dist/core/PlanningEntities.test.js +0 -509
  160. package/dist/core/ScheduleFields.test.d.ts +0 -1
  161. package/dist/core/ScheduleFields.test.js +0 -193
  162. package/dist/core/SetupState.test.d.ts +0 -1
  163. package/dist/core/SetupState.test.js +0 -113
  164. package/dist/core/SignupMonetizationSettings.test.d.ts +0 -1
  165. package/dist/core/SignupMonetizationSettings.test.js +0 -515
  166. package/dist/core/Stats.test.d.ts +0 -1
  167. package/dist/core/Stats.test.js +0 -36
  168. package/dist/core/SystemAdmin.test.d.ts +0 -1
  169. package/dist/core/SystemAdmin.test.js +0 -1106
  170. package/dist/core/TaskActivitySync.test.d.ts +0 -1
  171. package/dist/core/TaskActivitySync.test.js +0 -90
  172. package/dist/core/TaskAttachmentsCanonical.test.d.ts +0 -1
  173. package/dist/core/TaskAttachmentsCanonical.test.js +0 -356
  174. package/dist/core/TaskAuditTimeline.test.d.ts +0 -1
  175. package/dist/core/TaskAuditTimeline.test.js +0 -319
  176. package/dist/core/TaskTaxonomyValidation.test.d.ts +0 -1
  177. package/dist/core/TaskTaxonomyValidation.test.js +0 -440
  178. package/dist/core/Taskforce.ids.test.d.ts +0 -1
  179. package/dist/core/Taskforce.ids.test.js +0 -611
  180. package/dist/core/Taskforce.listTasksSlim.test.d.ts +0 -1
  181. package/dist/core/Taskforce.listTasksSlim.test.js +0 -91
  182. package/dist/core/Taskforce.mcpAuth.test.d.ts +0 -1
  183. package/dist/core/Taskforce.mcpAuth.test.js +0 -450
  184. package/dist/core/Taskforce.realtimeMutation.test.d.ts +0 -1
  185. package/dist/core/Taskforce.realtimeMutation.test.js +0 -131
  186. package/dist/core/UserProfileAvatarDrafts.test.d.ts +0 -1
  187. package/dist/core/UserProfileAvatarDrafts.test.js +0 -138
  188. package/dist/core/WorkspaceDelete.test.d.ts +0 -1
  189. package/dist/core/WorkspaceDelete.test.js +0 -159
  190. package/dist/core/WorkspacePermissions.test.d.ts +0 -1
  191. package/dist/core/WorkspacePermissions.test.js +0 -270
  192. package/dist/core/WorkspacePlanMode.test.d.ts +0 -1
  193. package/dist/core/WorkspacePlanMode.test.js +0 -383
  194. package/dist/core/WorkspaceSyncSettings.test.d.ts +0 -1
  195. package/dist/core/WorkspaceSyncSettings.test.js +0 -65
  196. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +0 -1
  197. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +0 -212
  198. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +0 -1
  199. package/dist/documentReviews/DocumentReviewSessionStore.test.js +0 -127
  200. package/dist/hooks/authGuardPolicy.test.d.ts +0 -1
  201. package/dist/hooks/authGuardPolicy.test.js +0 -76
  202. package/dist/hooks/bootstrapTimeouts.test.d.ts +0 -1
  203. package/dist/hooks/bootstrapTimeouts.test.js +0 -12
  204. package/dist/hooks/sync/controlPlane.test.d.ts +0 -1
  205. package/dist/hooks/sync/controlPlane.test.js +0 -121
  206. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +0 -1
  207. package/dist/hooks/sync/lifecyclePolicy.test.js +0 -124
  208. package/dist/hooks/sync/orchestratorShared.test.d.ts +0 -1
  209. package/dist/hooks/sync/orchestratorShared.test.js +0 -98
  210. package/dist/hooks/sync/pullLifecycle.test.d.ts +0 -1
  211. package/dist/hooks/sync/pullLifecycle.test.js +0 -80
  212. package/dist/hooks/sync/recovery.test.d.ts +0 -1
  213. package/dist/hooks/sync/recovery.test.js +0 -292
  214. package/dist/hooks/sync/transfers.test.d.ts +0 -1
  215. package/dist/hooks/sync/transfers.test.js +0 -928
  216. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +0 -1
  217. package/dist/hooks/sync/useRecentSyncEvents.test.js +0 -124
  218. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +0 -1
  219. package/dist/hooks/sync/useSyncStatusActions.test.js +0 -133
  220. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +0 -1
  221. package/dist/hooks/sync/useSyncStatusControls.test.js +0 -88
  222. package/dist/hooks/useFilterSort.test.d.ts +0 -1
  223. package/dist/hooks/useFilterSort.test.js +0 -419
  224. package/dist/hooks/useRealtimeSync.test.d.ts +0 -1
  225. package/dist/hooks/useRealtimeSync.test.js +0 -311
  226. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +0 -1
  227. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +0 -54
  228. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +0 -1
  229. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +0 -228
  230. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +0 -1
  231. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +0 -2027
  232. package/dist/hooks/useTaskData.test.d.ts +0 -1
  233. package/dist/hooks/useTaskData.test.js +0 -291
  234. package/dist/hooks/useTaskMutations.test.d.ts +0 -1
  235. package/dist/hooks/useTaskMutations.test.js +0 -256
  236. package/dist/hooks/useTaskforce.contract.test.d.ts +0 -1
  237. package/dist/hooks/useTaskforce.contract.test.js +0 -7
  238. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +0 -1
  239. package/dist/hooks/useTaskforce.priority-filters.test.js +0 -28
  240. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +0 -1
  241. package/dist/hooks/useTaskforce.runtime-routing.test.js +0 -246
  242. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +0 -1
  243. package/dist/hooks/useTaskforce.sync-behavior.test.js +0 -3644
  244. package/dist/hooks/useWorkspaceSyncController.test.d.ts +0 -1
  245. package/dist/hooks/useWorkspaceSyncController.test.js +0 -192
  246. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +0 -1
  247. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +0 -77
  248. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +0 -1
  249. package/dist/hooks/workspace/workspaceLocalState.test.js +0 -59
  250. package/dist/localization/localizationCatalogs.test.d.ts +0 -1
  251. package/dist/localization/localizationCatalogs.test.js +0 -37
  252. package/dist/mcp/aiProfileBootstrap.test.d.ts +0 -1
  253. package/dist/mcp/aiProfileBootstrap.test.js +0 -68
  254. package/dist/mcp/canonicalAssetHelpers.test.d.ts +0 -1
  255. package/dist/mcp/canonicalAssetHelpers.test.js +0 -67
  256. package/dist/mcp/clientIntegrations.test.d.ts +0 -1
  257. package/dist/mcp/clientIntegrations.test.js +0 -219
  258. package/dist/mcp/optionResolution.test.d.ts +0 -1
  259. package/dist/mcp/optionResolution.test.js +0 -43
  260. package/dist/mcp/runtime.test.d.ts +0 -1
  261. package/dist/mcp/runtime.test.js +0 -5120
  262. package/dist/mcp/structuredCommentValidation.test.d.ts +0 -1
  263. package/dist/mcp/structuredCommentValidation.test.js +0 -31
  264. package/dist/mcp/structuredDocValidation.test.d.ts +0 -1
  265. package/dist/mcp/structuredDocValidation.test.js +0 -35
  266. package/dist/mcp/toolRegistry.test.d.ts +0 -1
  267. package/dist/mcp/toolRegistry.test.js +0 -34
  268. package/dist/migrations/billingSchemaParity.test.d.ts +0 -1
  269. package/dist/migrations/billingSchemaParity.test.js +0 -160
  270. package/dist/migrations/taskSchemaMigrations.test.d.ts +0 -1
  271. package/dist/migrations/taskSchemaMigrations.test.js +0 -225
  272. package/dist/qaOpen.test.d.ts +0 -1
  273. package/dist/qaOpen.test.js +0 -56
  274. package/dist/releaseVersion.test.d.ts +0 -1
  275. package/dist/releaseVersion.test.js +0 -32
  276. package/dist/runtime/appGates.test.d.ts +0 -1
  277. package/dist/runtime/appGates.test.js +0 -42
  278. package/dist/server/aiProfileAvatarAssets.test.d.ts +0 -1
  279. package/dist/server/aiProfileAvatarAssets.test.js +0 -213
  280. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +0 -1
  281. package/dist/server/annotatedAttachmentsRoutes.test.js +0 -532
  282. package/dist/server/auth.test.d.ts +0 -1
  283. package/dist/server/auth.test.js +0 -131
  284. package/dist/server/buildInfo.test.d.ts +0 -1
  285. package/dist/server/buildInfo.test.js +0 -29
  286. package/dist/server/cors.test.d.ts +0 -1
  287. package/dist/server/cors.test.js +0 -77
  288. package/dist/server/documentReviewRoutes.test.d.ts +0 -1
  289. package/dist/server/documentReviewRoutes.test.js +0 -190
  290. package/dist/server/email.test.d.ts +0 -1
  291. package/dist/server/email.test.js +0 -113
  292. package/dist/server/index.cookieProxy.test.d.ts +0 -1
  293. package/dist/server/index.cookieProxy.test.js +0 -67
  294. package/dist/server/index.rateLimit.test.d.ts +0 -1
  295. package/dist/server/index.rateLimit.test.js +0 -22
  296. package/dist/server/index.test.d.ts +0 -1
  297. package/dist/server/index.test.js +0 -283
  298. package/dist/server/rateLimit.test.d.ts +0 -1
  299. package/dist/server/rateLimit.test.js +0 -107
  300. package/dist/server/realtimeSync.auth.test.d.ts +0 -1
  301. package/dist/server/realtimeSync.auth.test.js +0 -62
  302. package/dist/server/realtimeSync.replay.test.d.ts +0 -1
  303. package/dist/server/realtimeSync.replay.test.js +0 -192
  304. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +0 -1
  305. package/dist/server/realtimeSync.workspaceIsolation.test.js +0 -101
  306. package/dist/server/routes/billing.test.d.ts +0 -1
  307. package/dist/server/routes/billing.test.js +0 -3455
  308. package/dist/server/routes/shared.test.d.ts +0 -1
  309. package/dist/server/routes/shared.test.js +0 -32
  310. package/dist/server/routes/sync.integration.test.d.ts +0 -1
  311. package/dist/server/routes/sync.integration.test.js +0 -2932
  312. package/dist/server/routes/syncAuxRoutes.test.d.ts +0 -1
  313. package/dist/server/routes/syncAuxRoutes.test.js +0 -158
  314. package/dist/server/routes/syncRouteGuards.test.d.ts +0 -1
  315. package/dist/server/routes/syncRouteGuards.test.js +0 -94
  316. package/dist/server/routes.test.d.ts +0 -1
  317. package/dist/server/routes.test.js +0 -15612
  318. package/dist/server/runtimeMode.test.d.ts +0 -1
  319. package/dist/server/runtimeMode.test.js +0 -26
  320. package/dist/server/securityHeaders.test.d.ts +0 -1
  321. package/dist/server/securityHeaders.test.js +0 -70
  322. package/dist/services/templateEngine.test.d.ts +0 -1
  323. package/dist/services/templateEngine.test.js +0 -150
  324. package/dist/services/templates.test.d.ts +0 -1
  325. package/dist/services/templates.test.js +0 -45
  326. package/dist/services/workflowDocumentFixtures.test.d.ts +0 -1
  327. package/dist/services/workflowDocumentFixtures.test.js +0 -78
  328. package/dist/services/workflowExportMatrix.test.d.ts +0 -1
  329. package/dist/services/workflowExportMatrix.test.js +0 -113
  330. package/dist/services/workflowExportSnapshots.test.d.ts +0 -1
  331. package/dist/services/workflowExportSnapshots.test.js +0 -30
  332. package/dist/services/workflowGeneration.test.d.ts +0 -1
  333. package/dist/services/workflowGeneration.test.js +0 -12
  334. package/dist/services/workflowSimulation.test.d.ts +0 -1
  335. package/dist/services/workflowSimulation.test.js +0 -113
  336. package/dist/services/workflowV2Policy.test.d.ts +0 -1
  337. package/dist/services/workflowV2Policy.test.js +0 -80
  338. package/dist/shared/customTaxonomyLibrary.test.d.ts +0 -1
  339. package/dist/shared/customTaxonomyLibrary.test.js +0 -20
  340. package/dist/shared/passwordPolicy.test.d.ts +0 -1
  341. package/dist/shared/passwordPolicy.test.js +0 -34
  342. package/dist/shared/runtimeContract.test.d.ts +0 -1
  343. package/dist/shared/runtimeContract.test.js +0 -23
  344. package/dist/shared/taxonomyLibrary.test.d.ts +0 -1
  345. package/dist/shared/taxonomyLibrary.test.js +0 -26
  346. package/dist/shared/taxonomyState.test.d.ts +0 -1
  347. package/dist/shared/taxonomyState.test.js +0 -71
  348. package/dist/soak/preflight.cli.d.ts +0 -1
  349. package/dist/soak/preflight.cli.js +0 -28
  350. package/dist/soak/preflight.d.ts +0 -26
  351. package/dist/soak/preflight.js +0 -113
  352. package/dist/soak/preflight.test.d.ts +0 -1
  353. package/dist/soak/preflight.test.js +0 -39
  354. package/dist/storage/objectStorageClient.test.d.ts +0 -1
  355. package/dist/storage/objectStorageClient.test.js +0 -82
  356. package/dist/storage/objectStorageConfig.test.d.ts +0 -1
  357. package/dist/storage/objectStorageConfig.test.js +0 -50
  358. package/dist/storage/postgresAdapter.test.d.ts +0 -1
  359. package/dist/storage/postgresAdapter.test.js +0 -29
  360. package/dist/storage/workspaceAssetStore.test.d.ts +0 -1
  361. package/dist/storage/workspaceAssetStore.test.js +0 -588
  362. package/dist/sync/cloudSyncApi.test.d.ts +0 -1
  363. package/dist/sync/cloudSyncApi.test.js +0 -111
  364. package/dist/sync/collaborationSyncPayload.test.d.ts +0 -1
  365. package/dist/sync/collaborationSyncPayload.test.js +0 -125
  366. package/dist/sync/contentSyncPayload.test.d.ts +0 -1
  367. package/dist/sync/contentSyncPayload.test.js +0 -143
  368. package/dist/sync/encryptionService.test.d.ts +0 -1
  369. package/dist/sync/encryptionService.test.js +0 -52
  370. package/dist/sync/planningSyncPayload.test.d.ts +0 -1
  371. package/dist/sync/planningSyncPayload.test.js +0 -92
  372. package/dist/sync/realtimeFeature.test.d.ts +0 -1
  373. package/dist/sync/realtimeFeature.test.js +0 -30
  374. package/dist/sync/realtimeReconcile.test.d.ts +0 -1
  375. package/dist/sync/realtimeReconcile.test.js +0 -43
  376. package/dist/sync/syncApplyHandlers.test.d.ts +0 -1
  377. package/dist/sync/syncApplyHandlers.test.js +0 -626
  378. package/dist/sync/syncService.test.d.ts +0 -1
  379. package/dist/sync/syncService.test.js +0 -903
  380. package/dist/sync/taskSyncPayload.test.d.ts +0 -1
  381. package/dist/sync/taskSyncPayload.test.js +0 -54
  382. package/dist/sync/workspacePullFeed.test.d.ts +0 -1
  383. package/dist/sync/workspacePullFeed.test.js +0 -438
  384. package/dist/sync/workspaceSyncModel.test.d.ts +0 -1
  385. package/dist/sync/workspaceSyncModel.test.js +0 -1442
  386. package/dist/test/setup.d.ts +0 -1
  387. package/dist/test/setup.js +0 -15
  388. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +0 -1
  389. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +0 -3
  390. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +0 -1
  391. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +0 -1
  392. package/dist/ui/assets/documentReferences-BOUcJm6-.js +0 -1
  393. package/dist/ui/assets/index-MAHKvFqp.js +0 -4
  394. package/dist/utils/accountProfileSummaryCache.test.d.ts +0 -1
  395. package/dist/utils/accountProfileSummaryCache.test.js +0 -63
  396. package/dist/utils/annotatedAttachments.test.d.ts +0 -1
  397. package/dist/utils/annotatedAttachments.test.js +0 -108
  398. package/dist/utils/assignees.test.d.ts +0 -1
  399. package/dist/utils/assignees.test.js +0 -25
  400. package/dist/utils/avatarUpload.test.d.ts +0 -1
  401. package/dist/utils/avatarUpload.test.js +0 -40
  402. package/dist/utils/canonicalAssetKind.test.d.ts +0 -1
  403. package/dist/utils/canonicalAssetKind.test.js +0 -24
  404. package/dist/utils/coalescedRefresh.test.d.ts +0 -1
  405. package/dist/utils/coalescedRefresh.test.js +0 -30
  406. package/dist/utils/constants.icons.test.d.ts +0 -1
  407. package/dist/utils/constants.icons.test.js +0 -41
  408. package/dist/utils/planParser.test.d.ts +0 -1
  409. package/dist/utils/planParser.test.js +0 -183
  410. package/dist/utils/planningReferences.test.d.ts +0 -1
  411. package/dist/utils/planningReferences.test.js +0 -17
  412. package/dist/utils/syncEventDetails.test.d.ts +0 -1
  413. package/dist/utils/syncEventDetails.test.js +0 -23
  414. package/dist/utils/syncEventPresentation.test.d.ts +0 -1
  415. package/dist/utils/syncEventPresentation.test.js +0 -64
  416. package/dist/utils/syncStatusPresentation.test.d.ts +0 -1
  417. package/dist/utils/syncStatusPresentation.test.js +0 -164
  418. package/dist/utils/taskActivity.test.d.ts +0 -1
  419. package/dist/utils/taskActivity.test.js +0 -218
  420. package/dist/utils/taskDraftDefaults.test.d.ts +0 -1
  421. package/dist/utils/taskDraftDefaults.test.js +0 -34
  422. package/dist/utils/taskDragZones.test.d.ts +0 -1
  423. package/dist/utils/taskDragZones.test.js +0 -14
  424. package/dist/utils/taskNormalization.test.d.ts +0 -1
  425. package/dist/utils/taskNormalization.test.js +0 -158
  426. package/dist/utils/taskSearch.test.d.ts +0 -1
  427. package/dist/utils/taskSearch.test.js +0 -69
  428. package/dist/utils/taskUtils.test.d.ts +0 -1
  429. package/dist/utils/taskUtils.test.js +0 -19
  430. package/dist/utils/taskforceApiClient.test.d.ts +0 -1
  431. package/dist/utils/taskforceApiClient.test.js +0 -44
  432. package/dist/utils/taxonomyFormDefaults.test.d.ts +0 -1
  433. package/dist/utils/taxonomyFormDefaults.test.js +0 -63
  434. package/dist/utils/uiNotice.test.d.ts +0 -1
  435. package/dist/utils/uiNotice.test.js +0 -18
  436. package/dist/utils/workspaceIdentity.test.d.ts +0 -1
  437. package/dist/utils/workspaceIdentity.test.js +0 -61
  438. package/dist/utils/workspaceLifecycle.test.d.ts +0 -1
  439. package/dist/utils/workspaceLifecycle.test.js +0 -64
  440. package/dist/utils/workspaceSyncPresentation.test.d.ts +0 -1
  441. package/dist/utils/workspaceSyncPresentation.test.js +0 -71
  442. package/scripts/bootstrap-playwright-auth.mjs +0 -155
  443. package/scripts/check-cloud-mcp.mjs +0 -83
  444. package/scripts/check-compat-boundaries.mjs +0 -41
  445. package/scripts/check-env.ts +0 -31
  446. package/scripts/check-localization-strings.mjs +0 -49
  447. package/scripts/check-runtime-boundaries.mjs +0 -44
  448. package/scripts/debug-global.mjs +0 -34
  449. package/scripts/debug-login.mjs +0 -32
  450. package/scripts/debug-server.mjs +0 -36
  451. package/scripts/deploy-prod.sh +0 -41
  452. package/scripts/deploy-staging.sh +0 -23
  453. package/scripts/export-sqlite-to-postgres.mjs +0 -123
  454. package/scripts/generate-env-example.ts +0 -8
  455. package/scripts/mcp-stdio-adapter.mjs +0 -160
  456. package/scripts/migrate-task-data-assets.mjs +0 -408
  457. package/scripts/playwright-auth.sh +0 -19
  458. package/scripts/qa-open-lib.mjs +0 -175
  459. package/scripts/qa-open-lib.mjs.d.ts +0 -49
  460. package/scripts/qa-open.mjs +0 -352
  461. package/scripts/release-version-lib.mjs +0 -88
  462. package/scripts/release-version.mjs +0 -72
  463. package/scripts/run-billing-performance-smoke.sh +0 -24
  464. package/scripts/run-dev-proxy.sh +0 -51
  465. package/scripts/run-e2e-realtime.sh +0 -30
  466. package/scripts/run-smoke.sh +0 -35
  467. package/scripts/run-soak.sh +0 -41
  468. package/scripts/test-global.js +0 -143
  469. package/scripts/update-homebrew-formula.sh +0 -108
  470. package/scripts/with-esbuild.sh +0 -11
@@ -1,1439 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
4
- import userEvent from '@testing-library/user-event';
5
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
- import { buildPrintDocumentHtml, DocumentViewer, schedulePrintCleanup } from './DocumentViewer';
7
- import { DocumentGenerateModal } from './DocumentGenerateModal';
8
- import { DocumentWorkspace, DocumentWorkspaceShell } from './DocumentWorkspace';
9
- const { fetchTaskforceApi } = vi.hoisted(() => ({
10
- fetchTaskforceApi: vi.fn(),
11
- }));
12
- vi.mock('../../utils/taskforceApiClient', () => ({
13
- fetchTaskforceApi,
14
- resolveTaskforceApiUrl: (path) => path,
15
- }));
16
- vi.mock('../../utils/documentManagerApi', async () => {
17
- const actual = await vi.importActual('../../utils/documentManagerApi');
18
- return {
19
- ...actual,
20
- logDocumentManagerDebug: vi.fn(),
21
- };
22
- });
23
- function createDoc(overrides = {}) {
24
- return {
25
- id: 'doc-1',
26
- assetId: 'asset-doc-1',
27
- fsPath: 'workspaces/default/assets/documents/technical-notes.md',
28
- apiUrl: '/api/taskforce/documents/asset-doc-1/content',
29
- taskId: null,
30
- displayName: 'Technical Notes',
31
- title: 'Technical Notes',
32
- docType: 'other',
33
- sizeBytes: 64,
34
- updatedAt: '2026-03-19T11:00:00.000Z',
35
- editable: true,
36
- attachmentCount: 0,
37
- attachmentState: 'unattached',
38
- ...overrides,
39
- };
40
- }
41
- function createEmptyReviewSessionsResponse() {
42
- return {
43
- ok: true,
44
- json: async () => ({ sessions: [] }),
45
- };
46
- }
47
- function renderDocumentWorkspace(props) {
48
- return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, documentTrayOpen: props.documentTrayOpen, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
49
- }
50
- describe('Document workspace management', () => {
51
- beforeEach(() => {
52
- fetchTaskforceApi.mockReset();
53
- });
54
- afterEach(() => {
55
- vi.useRealTimers();
56
- vi.restoreAllMocks();
57
- });
58
- it('filters documents by unattached status and shows a badge in the index', async () => {
59
- const docs = [
60
- {
61
- id: 'doc-attached',
62
- assetId: 'doc-attached',
63
- fsPath: 'workspaces/default/assets/documents/doc-attached.md',
64
- apiUrl: '/api/taskforce/documents/doc-attached/content',
65
- taskId: 'task-1',
66
- displayName: 'Attached notes',
67
- title: 'Attached notes',
68
- docType: 'other',
69
- sizeBytes: 128,
70
- updatedAt: '2026-03-19T10:00:00.000Z',
71
- editable: true,
72
- attachmentCount: 1,
73
- attachmentState: 'attached',
74
- },
75
- {
76
- id: 'doc-unattached',
77
- assetId: 'doc-unattached',
78
- fsPath: 'workspaces/default/assets/documents/doc-unattached.md',
79
- apiUrl: '/api/taskforce/documents/doc-unattached/content',
80
- taskId: null,
81
- displayName: 'Orphan notes',
82
- title: 'Orphan notes',
83
- docType: 'other',
84
- sizeBytes: 64,
85
- updatedAt: '2026-03-19T11:00:00.000Z',
86
- editable: true,
87
- attachmentCount: 0,
88
- attachmentState: 'unattached',
89
- },
90
- ];
91
- function Harness() {
92
- const [selectedDoc, setSelectedDoc] = useState(null);
93
- return (_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs.filter((doc) => doc.attachmentState === 'unattached'), loading: false, error: null, selectedDoc: selectedDoc, searchQuery: "", hasActiveFilters: true, onSearchChange: () => { }, onSelectDoc: setSelectedDoc, onRefresh: () => { }, onDeleteSelected: () => { } }));
94
- }
95
- render(_jsx(Harness, {}));
96
- expect(screen.getByRole('button', { name: /Orphan notes/i })).toBeInTheDocument();
97
- expect(screen.queryByRole('button', { name: /Attached notes/i })).not.toBeInTheDocument();
98
- expect(screen.getByText('Unattached')).toBeInTheDocument();
99
- });
100
- it('can render the document index as a collapsible tray', () => {
101
- const docs = [createDoc({ id: 'tray-doc', displayName: 'Tray Document' })];
102
- const { container, rerender } = renderDocumentWorkspace({
103
- documents: docs,
104
- documentTrayOpen: true,
105
- });
106
- expect(container.querySelector('[aria-hidden="false"]')).toHaveTextContent('Tray Document');
107
- rerender(_jsx(DocumentWorkspace, { tasks: [], documents: docs, loading: false, error: null, selectedDoc: null, searchQuery: "", hasActiveFilters: false, documentTrayOpen: false, onSearchChange: () => { }, onSelectDoc: () => { }, onRefresh: () => { }, onDeleteSelected: () => { } }));
108
- expect(container.querySelector('[aria-hidden="true"]')).toHaveTextContent('Tray Document');
109
- });
110
- it('keeps document search in the index without duplicate filter controls', () => {
111
- renderDocumentWorkspace({ documents: [createDoc()] });
112
- expect(screen.getByPlaceholderText('Search documents…')).toBeInTheDocument();
113
- expect(screen.queryByRole('button', { name: 'Attached' })).not.toBeInTheDocument();
114
- expect(screen.queryByRole('button', { name: 'Plans' })).not.toBeInTheDocument();
115
- });
116
- it('shows the document reference badge in the bottom metadata row of index cards', () => {
117
- renderDocumentWorkspace({ documents: [createDoc({ referenceLabel: 'D-42' })] });
118
- expect(screen.getByText('D-42')).toBeInTheDocument();
119
- expect(screen.getByText(/64B/)).toBeInTheDocument();
120
- });
121
- it('shows permanent delete only for unattached docs and deletes after confirmation', async () => {
122
- const user = userEvent.setup();
123
- fetchTaskforceApi
124
- .mockResolvedValueOnce({
125
- ok: true,
126
- text: async () => '# Example\n',
127
- })
128
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
129
- .mockResolvedValueOnce({
130
- ok: true,
131
- json: async () => ({ success: true, softDeleted: true }),
132
- });
133
- const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
134
- const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => { });
135
- const onDeleted = vi.fn();
136
- render(_jsx(DocumentViewer, { doc: {
137
- id: 'doc-unattached',
138
- assetId: 'asset-doc-unattached',
139
- fsPath: 'workspaces/default/assets/documents/doc-unattached.md',
140
- apiUrl: '/api/taskforce/documents/asset-doc-unattached/content',
141
- taskId: null,
142
- displayName: 'Unattached spec',
143
- title: 'Unattached spec',
144
- docType: 'other',
145
- sizeBytes: 64,
146
- updatedAt: '2026-03-19T11:00:00.000Z',
147
- editable: true,
148
- attachmentCount: 0,
149
- attachmentState: 'unattached',
150
- }, taskTitle: null, onDeleted: onDeleted }));
151
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-unattached/content', { credentials: 'same-origin' }, ''));
152
- await user.click(screen.getByRole('button', { name: /Delete Permanently/i }));
153
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-unattached', expect.objectContaining({ method: 'DELETE', credentials: 'include' }), ''));
154
- expect(onDeleted).toHaveBeenCalled();
155
- confirmSpy.mockRestore();
156
- alertSpy.mockRestore();
157
- });
158
- it('opens an attach modal from the document viewer and attaches the document to a task', async () => {
159
- const user = userEvent.setup();
160
- fetchTaskforceApi
161
- .mockResolvedValueOnce({
162
- ok: true,
163
- text: async () => '# Example\n',
164
- })
165
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
166
- .mockResolvedValueOnce({
167
- ok: true,
168
- json: async () => ({ success: true, attached: true, alreadyAttached: false }),
169
- });
170
- const onSaved = vi.fn();
171
- render(_jsx(DocumentViewer, { doc: createDoc({
172
- id: 'doc-attach',
173
- assetId: 'asset-doc-attach',
174
- attachmentCount: 0,
175
- attachmentState: 'unattached',
176
- }), taskTitle: null, tasks: [
177
- { id: 'task-1', title: 'First attach target', referenceLabel: 'T-11' },
178
- { id: 'task-2', title: 'Second attach target', referenceLabel: 'T-12' },
179
- ], onSaved: onSaved }));
180
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-1/content', { credentials: 'same-origin' }, ''));
181
- await user.click(screen.getByRole('button', { name: /^Attach$/i }));
182
- expect(screen.getByText(/Attach this document to a task from the document manager/i)).toBeInTheDocument();
183
- expect(screen.getByText('T-11')).toBeInTheDocument();
184
- expect(screen.getByText('T-12')).toBeInTheDocument();
185
- await user.clear(screen.getByPlaceholderText('Search tasks…'));
186
- await user.type(screen.getByPlaceholderText('Search tasks…'), 'T-12');
187
- expect(screen.queryByRole('button', { name: /First attach target/i })).not.toBeInTheDocument();
188
- expect(screen.getByRole('button', { name: /Second attach target/i })).toBeInTheDocument();
189
- await user.clear(screen.getByPlaceholderText('Search tasks…'));
190
- await user.type(screen.getByPlaceholderText('Search tasks…'), 'Second');
191
- await user.click(screen.getByRole('button', { name: /Second attach target/i }));
192
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attach/attach', expect.objectContaining({
193
- method: 'POST',
194
- credentials: 'include',
195
- }), ''));
196
- expect(onSaved).toHaveBeenCalled();
197
- });
198
- it('shows delete and unlink for attached docs and sends forceDetach on delete', async () => {
199
- const user = userEvent.setup();
200
- fetchTaskforceApi
201
- .mockResolvedValueOnce({
202
- ok: true,
203
- text: async () => '# Example\n',
204
- })
205
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
206
- .mockResolvedValueOnce({
207
- ok: true,
208
- json: async () => ({ success: true, softDeleted: true, detachedTaskCount: 2 }),
209
- });
210
- const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
211
- const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => { });
212
- const onDeleted = vi.fn();
213
- render(_jsx(DocumentViewer, { doc: {
214
- id: 'doc-attached',
215
- assetId: 'asset-doc-attached',
216
- fsPath: 'workspaces/default/assets/documents/doc-attached.md',
217
- apiUrl: '/api/taskforce/documents/asset-doc-attached/content',
218
- taskId: 'task-1',
219
- displayName: 'Attached spec',
220
- title: 'Attached spec',
221
- docType: 'other',
222
- sizeBytes: 64,
223
- updatedAt: '2026-03-19T11:00:00.000Z',
224
- editable: true,
225
- attachmentCount: 2,
226
- attachmentState: 'attached',
227
- }, taskTitle: null, onDeleted: onDeleted }));
228
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-attached/content', { credentials: 'same-origin' }, ''));
229
- await user.click(screen.getByRole('button', { name: /Delete & Unlink/i }));
230
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attached', expect.objectContaining({
231
- method: 'DELETE',
232
- credentials: 'include',
233
- headers: { 'Content-Type': 'application/json' },
234
- body: JSON.stringify({ forceDetach: true })
235
- }), ''));
236
- expect(confirmSpy).toHaveBeenCalledWith(expect.stringContaining('remove it from 2 linked tasks'));
237
- expect(onDeleted).toHaveBeenCalled();
238
- confirmSpy.mockRestore();
239
- alertSpy.mockRestore();
240
- });
241
- it('uses same-origin document routes in local runtime even when an api base is configured', async () => {
242
- const user = userEvent.setup();
243
- fetchTaskforceApi
244
- .mockResolvedValueOnce({
245
- ok: true,
246
- json: async () => ({ documents: [createDoc()] }),
247
- })
248
- .mockResolvedValueOnce({
249
- ok: true,
250
- text: async () => '# Example\n',
251
- })
252
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
253
- .mockResolvedValueOnce({
254
- ok: true,
255
- json: async () => ({ updatedAt: '2026-03-19T12:00:00.000Z' }),
256
- });
257
- render(_jsx(DocumentWorkspaceShell, { tasks: [], runtimeMode: "local", apiBaseUrl: "http://127.0.0.1:4280", cloudAuthBaseUrl: "https://cloud.example.com" }));
258
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(1, '/api/taskforce/documents', expect.objectContaining({ credentials: 'include' }), ''));
259
- await screen.findByText('Technical Notes');
260
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(2, '/api/taskforce/documents/asset-doc-1/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
261
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions?assetId=asset-doc-1', expect.objectContaining({ credentials: 'include' }), ''));
262
- await user.click(screen.getByRole('button', { name: /Edit/i }));
263
- const editor = await screen.findByPlaceholderText('Write markdown here…');
264
- await user.clear(editor);
265
- await user.type(editor, '# Updated\n');
266
- await user.click(screen.getByRole('button', { name: /^Save$/i }));
267
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/documents/save', expect.objectContaining({
268
- method: 'POST',
269
- credentials: 'include',
270
- headers: { 'Content-Type': 'application/json' },
271
- }), ''));
272
- });
273
- it('renders document review comments from the document review session API', async () => {
274
- fetchTaskforceApi
275
- .mockResolvedValueOnce({
276
- ok: true,
277
- text: async () => '# Example\n',
278
- })
279
- .mockResolvedValueOnce({
280
- ok: true,
281
- json: async () => ({
282
- sessions: [{
283
- id: 'review-1',
284
- tenantId: 'default',
285
- workspaceId: 'default',
286
- assetId: 'asset-doc-1',
287
- documentId: 'doc-1',
288
- documentVersion: 1,
289
- title: 'Review 1',
290
- status: 'open',
291
- createdByActorId: 'user',
292
- updatedByActorId: 'user',
293
- createdAt: '2026-03-19T12:00:00.000Z',
294
- updatedAt: '2026-03-19T12:10:00.000Z',
295
- deletedAt: null,
296
- createdByActor: null,
297
- updatedByActor: null,
298
- comments: [{
299
- id: 'comment-1',
300
- order: 0,
301
- body: 'Please tighten the rollout section.',
302
- anchor: { kind: 'line-range', startLine: 18, endLine: 24 },
303
- authorActorId: 'user',
304
- authorActor: {
305
- id: 'user',
306
- kind: 'human',
307
- label: 'You',
308
- username: 'you',
309
- icon: 'User',
310
- color: '#5b8def',
311
- isNamed: true,
312
- },
313
- createdAt: '2026-03-19T12:05:00.000Z',
314
- updatedAt: '2026-03-19T12:05:00.000Z',
315
- deletedAt: null,
316
- }],
317
- }],
318
- }),
319
- });
320
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
321
- expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
322
- expect(await screen.findByText('Please tighten the rollout section.')).toBeInTheDocument();
323
- expect(screen.getByText('Lines 18-24')).toBeInTheDocument();
324
- });
325
- it('toggles the review sidebar from the document header', async () => {
326
- const user = userEvent.setup();
327
- fetchTaskforceApi
328
- .mockResolvedValueOnce({
329
- ok: true,
330
- text: async () => '# Example\n',
331
- })
332
- .mockResolvedValueOnce({
333
- ok: true,
334
- json: async () => ({
335
- sessions: [{
336
- id: 'review-1',
337
- tenantId: 'default',
338
- workspaceId: 'default',
339
- assetId: 'asset-doc-1',
340
- documentId: 'doc-1',
341
- documentVersion: 1,
342
- title: 'Review 1',
343
- status: 'open',
344
- createdByActorId: 'user',
345
- updatedByActorId: 'user',
346
- createdByActor: null,
347
- updatedByActor: null,
348
- createdAt: '2026-03-19T12:00:00.000Z',
349
- updatedAt: '2026-03-19T12:05:00.000Z',
350
- deletedAt: null,
351
- comments: [],
352
- }],
353
- }),
354
- });
355
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
356
- expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
357
- expect(screen.getByPlaceholderText('Add a comment for this document review…')).toBeInTheDocument();
358
- await user.click(screen.getByRole('button', { name: 'Hide Review' }));
359
- expect(screen.queryByText('Review & Comments')).not.toBeInTheDocument();
360
- expect(screen.queryByPlaceholderText('Add a comment for this document review…')).not.toBeInTheDocument();
361
- await user.click(screen.getByRole('button', { name: 'Show Review' }));
362
- expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
363
- expect(screen.getByPlaceholderText('Add a comment for this document review…')).toBeInTheDocument();
364
- });
365
- it('creates a review session and posts comments from the document viewer', async () => {
366
- const user = userEvent.setup();
367
- fetchTaskforceApi
368
- .mockResolvedValueOnce({
369
- ok: true,
370
- text: async () => '# Example\n',
371
- })
372
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
373
- .mockResolvedValueOnce({
374
- ok: true,
375
- json: async () => ({
376
- success: true,
377
- session: {
378
- id: 'review-1',
379
- tenantId: 'default',
380
- workspaceId: 'default',
381
- assetId: 'asset-doc-1',
382
- documentId: 'doc-1',
383
- documentVersion: 1,
384
- title: 'Review 1',
385
- status: 'open',
386
- createdByActorId: 'user',
387
- updatedByActorId: 'user',
388
- createdAt: '2026-03-19T12:00:00.000Z',
389
- updatedAt: '2026-03-19T12:00:00.000Z',
390
- deletedAt: null,
391
- createdByActor: null,
392
- updatedByActor: null,
393
- comments: [],
394
- },
395
- }),
396
- })
397
- .mockResolvedValueOnce({
398
- ok: true,
399
- json: async () => ({
400
- success: true,
401
- session: {
402
- id: 'review-1',
403
- tenantId: 'default',
404
- workspaceId: 'default',
405
- assetId: 'asset-doc-1',
406
- documentId: 'doc-1',
407
- documentVersion: 1,
408
- title: 'Review 1',
409
- status: 'open',
410
- createdByActorId: 'user',
411
- updatedByActorId: 'user',
412
- createdAt: '2026-03-19T12:00:00.000Z',
413
- updatedAt: '2026-03-19T12:04:00.000Z',
414
- deletedAt: null,
415
- createdByActor: null,
416
- updatedByActor: null,
417
- comments: [{
418
- id: 'comment-1',
419
- order: 0,
420
- body: 'Needs a stronger summary.',
421
- anchor: { kind: 'document' },
422
- authorActorId: 'user',
423
- authorActor: null,
424
- createdAt: '2026-03-19T12:04:00.000Z',
425
- updatedAt: '2026-03-19T12:04:00.000Z',
426
- deletedAt: null,
427
- }],
428
- },
429
- }),
430
- });
431
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
432
- await screen.findByText('Review & Comments');
433
- await user.type(screen.getByPlaceholderText('Add a comment for this document review…'), 'Needs a stronger summary.');
434
- await user.click(screen.getByRole('button', { name: /Send review comment/i }));
435
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions', expect.objectContaining({
436
- method: 'POST',
437
- credentials: 'include',
438
- headers: { 'Content-Type': 'application/json' },
439
- }), ''));
440
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/document-reviews/sessions/review-1/comments', expect.objectContaining({
441
- method: 'POST',
442
- credentials: 'include',
443
- headers: { 'Content-Type': 'application/json' },
444
- }), ''));
445
- expect(await screen.findByText('Needs a stronger summary.')).toBeInTheDocument();
446
- });
447
- it('anchors a review comment to selected document text', async () => {
448
- const user = userEvent.setup();
449
- const getSelectionSpy = vi.spyOn(window, 'getSelection');
450
- fetchTaskforceApi
451
- .mockResolvedValueOnce({
452
- ok: true,
453
- text: async () => '# Example\n\nSelected rollout sentence for review.\n',
454
- })
455
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
456
- .mockResolvedValueOnce({
457
- ok: true,
458
- json: async () => ({
459
- success: true,
460
- session: {
461
- id: 'review-1',
462
- tenantId: 'default',
463
- workspaceId: 'default',
464
- assetId: 'asset-doc-1',
465
- documentId: 'doc-1',
466
- documentVersion: 1,
467
- title: 'Review 1',
468
- status: 'open',
469
- createdByActorId: 'user',
470
- updatedByActorId: 'user',
471
- createdAt: '2026-03-19T12:00:00.000Z',
472
- updatedAt: '2026-03-19T12:00:00.000Z',
473
- deletedAt: null,
474
- createdByActor: null,
475
- updatedByActor: null,
476
- comments: [],
477
- },
478
- }),
479
- })
480
- .mockResolvedValueOnce({
481
- ok: true,
482
- json: async () => ({
483
- success: true,
484
- session: {
485
- id: 'review-1',
486
- tenantId: 'default',
487
- workspaceId: 'default',
488
- assetId: 'asset-doc-1',
489
- documentId: 'doc-1',
490
- documentVersion: 1,
491
- title: 'Review 1',
492
- status: 'open',
493
- createdByActorId: 'user',
494
- updatedByActorId: 'user',
495
- createdAt: '2026-03-19T12:00:00.000Z',
496
- updatedAt: '2026-03-19T12:04:00.000Z',
497
- deletedAt: null,
498
- createdByActor: null,
499
- updatedByActor: null,
500
- comments: [{
501
- id: 'comment-1',
502
- order: 0,
503
- body: 'This sentence needs stronger ownership.',
504
- anchor: { kind: 'quote', quote: 'Selected rollout sentence for review.' },
505
- authorActorId: 'user',
506
- authorActor: null,
507
- createdAt: '2026-03-19T12:04:00.000Z',
508
- updatedAt: '2026-03-19T12:04:00.000Z',
509
- deletedAt: null,
510
- }],
511
- },
512
- }),
513
- });
514
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
515
- const selectedText = await screen.findByText('Selected rollout sentence for review.');
516
- const selectionNode = selectedText.firstChild || selectedText;
517
- getSelectionSpy.mockReturnValue({
518
- toString: () => 'Selected rollout sentence for review.',
519
- rangeCount: 1,
520
- getRangeAt: () => ({
521
- commonAncestorContainer: selectionNode,
522
- startContainer: selectionNode,
523
- startOffset: 0,
524
- endContainer: selectionNode,
525
- endOffset: 'Selected rollout sentence for review.'.length,
526
- }),
527
- });
528
- fireEvent.mouseUp(selectedText);
529
- expect(await screen.findByText('"Selected rollout sentence for review."')).toBeInTheDocument();
530
- await user.type(screen.getByPlaceholderText('Add a comment for the selected text…'), 'This sentence needs stronger ownership.');
531
- await user.click(screen.getByRole('button', { name: /Send review comment/i }));
532
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/document-reviews/sessions/review-1/comments', expect.objectContaining({
533
- method: 'POST',
534
- credentials: 'include',
535
- headers: { 'Content-Type': 'application/json' },
536
- }), ''));
537
- const commentRequest = fetchTaskforceApi.mock.calls[3]?.[1];
538
- expect(commentRequest).toBeTruthy();
539
- const requestBody = JSON.parse(String(commentRequest.body || '{}'));
540
- expect(requestBody.anchor).toMatchObject({
541
- kind: 'quote',
542
- quote: 'Selected rollout sentence for review.',
543
- });
544
- getSelectionSpy.mockRestore();
545
- });
546
- it('preserves the selected quote occurrence when text repeats in the document', async () => {
547
- const user = userEvent.setup();
548
- const getSelectionSpy = vi.spyOn(window, 'getSelection');
549
- fetchTaskforceApi
550
- .mockResolvedValueOnce({
551
- ok: true,
552
- text: async () => '# Example\n\nRepeated sentence.\n\nSpacer paragraph.\n\nRepeated sentence.\n',
553
- })
554
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
555
- .mockResolvedValueOnce({
556
- ok: true,
557
- json: async () => ({
558
- success: true,
559
- session: {
560
- id: 'review-1',
561
- tenantId: 'default',
562
- workspaceId: 'default',
563
- assetId: 'asset-doc-1',
564
- documentId: 'doc-1',
565
- documentVersion: 1,
566
- title: 'Review 1',
567
- status: 'open',
568
- createdByActorId: 'user',
569
- updatedByActorId: 'user',
570
- createdAt: '2026-03-19T12:00:00.000Z',
571
- updatedAt: '2026-03-19T12:00:00.000Z',
572
- deletedAt: null,
573
- createdByActor: null,
574
- updatedByActor: null,
575
- comments: [],
576
- },
577
- }),
578
- })
579
- .mockResolvedValueOnce({
580
- ok: true,
581
- json: async () => ({
582
- success: true,
583
- session: {
584
- id: 'review-1',
585
- tenantId: 'default',
586
- workspaceId: 'default',
587
- assetId: 'asset-doc-1',
588
- documentId: 'doc-1',
589
- documentVersion: 1,
590
- title: 'Review 1',
591
- status: 'open',
592
- createdByActorId: 'user',
593
- updatedByActorId: 'user',
594
- createdAt: '2026-03-19T12:00:00.000Z',
595
- updatedAt: '2026-03-19T12:04:00.000Z',
596
- deletedAt: null,
597
- createdByActor: null,
598
- updatedByActor: null,
599
- comments: [{
600
- id: 'comment-1',
601
- order: 0,
602
- body: 'Use the second repeated sentence.',
603
- anchor: { kind: 'quote', quote: 'Repeated sentence.', occurrenceIndex: 1 },
604
- authorActorId: 'user',
605
- authorActor: null,
606
- createdAt: '2026-03-19T12:04:00.000Z',
607
- updatedAt: '2026-03-19T12:04:00.000Z',
608
- deletedAt: null,
609
- }],
610
- },
611
- }),
612
- });
613
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
614
- const repeatedTextNodes = await screen.findAllByText('Repeated sentence.');
615
- const secondRepeatedText = repeatedTextNodes[1];
616
- const secondSelectionNode = secondRepeatedText.firstChild || secondRepeatedText;
617
- getSelectionSpy.mockReturnValue({
618
- toString: () => 'Repeated sentence.',
619
- rangeCount: 1,
620
- getRangeAt: () => ({
621
- commonAncestorContainer: secondSelectionNode,
622
- startContainer: secondSelectionNode,
623
- startOffset: 0,
624
- endContainer: secondSelectionNode,
625
- endOffset: 'Repeated sentence.'.length,
626
- }),
627
- });
628
- fireEvent.mouseUp(secondRepeatedText);
629
- await user.type(screen.getByPlaceholderText('Add a comment for the selected text…'), 'Use the second repeated sentence.');
630
- await user.click(screen.getByRole('button', { name: /Send review comment/i }));
631
- const commentRequest = fetchTaskforceApi.mock.calls[3]?.[1];
632
- expect(commentRequest).toBeTruthy();
633
- const requestBody = JSON.parse(String(commentRequest.body || '{}'));
634
- expect(requestBody.anchor).toMatchObject({
635
- kind: 'quote',
636
- quote: 'Repeated sentence.',
637
- occurrenceIndex: 1,
638
- });
639
- getSelectionSpy.mockRestore();
640
- });
641
- it('renames a document review session from the viewer', async () => {
642
- const user = userEvent.setup();
643
- fetchTaskforceApi
644
- .mockResolvedValueOnce({
645
- ok: true,
646
- text: async () => '# Example\n',
647
- })
648
- .mockResolvedValueOnce({
649
- ok: true,
650
- json: async () => ({
651
- sessions: [{
652
- id: 'review-1',
653
- tenantId: 'default',
654
- workspaceId: 'default',
655
- assetId: 'asset-doc-1',
656
- documentId: 'doc-1',
657
- documentVersion: 1,
658
- title: 'Review 1',
659
- status: 'open',
660
- createdByActorId: 'user',
661
- updatedByActorId: 'user',
662
- createdAt: '2026-03-19T12:00:00.000Z',
663
- updatedAt: '2026-03-19T12:10:00.000Z',
664
- deletedAt: null,
665
- createdByActor: null,
666
- updatedByActor: null,
667
- comments: [],
668
- }],
669
- }),
670
- })
671
- .mockResolvedValueOnce({
672
- ok: true,
673
- json: async () => ({
674
- success: true,
675
- session: {
676
- id: 'review-1',
677
- tenantId: 'default',
678
- workspaceId: 'default',
679
- assetId: 'asset-doc-1',
680
- documentId: 'doc-1',
681
- documentVersion: 1,
682
- title: 'Architecture Review',
683
- status: 'open',
684
- createdByActorId: 'user',
685
- updatedByActorId: 'user',
686
- createdAt: '2026-03-19T12:00:00.000Z',
687
- updatedAt: '2026-03-19T12:12:00.000Z',
688
- deletedAt: null,
689
- createdByActor: null,
690
- updatedByActor: null,
691
- comments: [],
692
- },
693
- }),
694
- });
695
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
696
- await screen.findByText('Review 1');
697
- await user.click(screen.getByRole('button', { name: /Rename Review/i }));
698
- const titleInput = screen.getByDisplayValue('Review 1');
699
- await user.clear(titleInput);
700
- await user.type(titleInput, 'Architecture Review');
701
- await user.click(screen.getByRole('button', { name: /Save Title/i }));
702
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions/review-1', expect.objectContaining({
703
- method: 'PATCH',
704
- credentials: 'include',
705
- headers: { 'Content-Type': 'application/json' },
706
- }), ''));
707
- expect(await screen.findByText('Architecture Review')).toBeInTheDocument();
708
- });
709
- it('clears a document review session title from the viewer', async () => {
710
- const user = userEvent.setup();
711
- fetchTaskforceApi
712
- .mockResolvedValueOnce({
713
- ok: true,
714
- text: async () => '# Example\n',
715
- })
716
- .mockResolvedValueOnce({
717
- ok: true,
718
- json: async () => ({
719
- sessions: [{
720
- id: 'review-1',
721
- tenantId: 'default',
722
- workspaceId: 'default',
723
- assetId: 'asset-doc-1',
724
- documentId: 'doc-1',
725
- documentVersion: 1,
726
- title: 'Review 1',
727
- status: 'open',
728
- createdByActorId: 'user',
729
- updatedByActorId: 'user',
730
- createdAt: '2026-03-19T12:00:00.000Z',
731
- updatedAt: '2026-03-19T12:10:00.000Z',
732
- deletedAt: null,
733
- createdByActor: null,
734
- updatedByActor: null,
735
- comments: [],
736
- }],
737
- }),
738
- })
739
- .mockResolvedValueOnce({
740
- ok: true,
741
- json: async () => ({
742
- success: true,
743
- session: {
744
- id: 'review-1',
745
- tenantId: 'default',
746
- workspaceId: 'default',
747
- assetId: 'asset-doc-1',
748
- documentId: 'doc-1',
749
- documentVersion: 1,
750
- title: null,
751
- status: 'open',
752
- createdByActorId: 'user',
753
- updatedByActorId: 'user',
754
- createdAt: '2026-03-19T12:00:00.000Z',
755
- updatedAt: '2026-03-19T12:12:00.000Z',
756
- deletedAt: null,
757
- createdByActor: null,
758
- updatedByActor: null,
759
- comments: [],
760
- },
761
- }),
762
- });
763
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
764
- await screen.findByText('Review 1');
765
- await user.click(screen.getByRole('button', { name: /Rename Review/i }));
766
- const titleInput = screen.getByDisplayValue('Review 1');
767
- await user.clear(titleInput);
768
- await user.click(screen.getByRole('button', { name: /Save Title/i }));
769
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions/review-1', expect.objectContaining({
770
- method: 'PATCH',
771
- credentials: 'include',
772
- headers: { 'Content-Type': 'application/json' },
773
- body: JSON.stringify({ title: null }),
774
- }), ''));
775
- expect(screen.queryByText('Review 1')).not.toBeInTheDocument();
776
- });
777
- it('edits a document review comment from the viewer', async () => {
778
- const user = userEvent.setup();
779
- fetchTaskforceApi
780
- .mockResolvedValueOnce({
781
- ok: true,
782
- text: async () => '# Example\n',
783
- })
784
- .mockResolvedValueOnce({
785
- ok: true,
786
- json: async () => ({
787
- sessions: [{
788
- id: 'review-1',
789
- tenantId: 'default',
790
- workspaceId: 'default',
791
- assetId: 'asset-doc-1',
792
- documentId: 'doc-1',
793
- documentVersion: 1,
794
- title: 'Review 1',
795
- status: 'open',
796
- createdByActorId: 'user',
797
- updatedByActorId: 'user',
798
- createdAt: '2026-03-19T12:00:00.000Z',
799
- updatedAt: '2026-03-19T12:10:00.000Z',
800
- deletedAt: null,
801
- createdByActor: null,
802
- updatedByActor: null,
803
- comments: [{
804
- id: 'comment-1',
805
- order: 0,
806
- body: 'Original review note.',
807
- anchor: { kind: 'document' },
808
- authorActorId: 'user',
809
- authorActor: null,
810
- createdAt: '2026-03-19T12:05:00.000Z',
811
- updatedAt: '2026-03-19T12:05:00.000Z',
812
- deletedAt: null,
813
- }],
814
- }],
815
- }),
816
- })
817
- .mockResolvedValueOnce({
818
- ok: true,
819
- json: async () => ({
820
- success: true,
821
- session: {
822
- id: 'review-1',
823
- tenantId: 'default',
824
- workspaceId: 'default',
825
- assetId: 'asset-doc-1',
826
- documentId: 'doc-1',
827
- documentVersion: 1,
828
- title: 'Review 1',
829
- status: 'open',
830
- createdByActorId: 'user',
831
- updatedByActorId: 'user',
832
- createdAt: '2026-03-19T12:00:00.000Z',
833
- updatedAt: '2026-03-19T12:12:00.000Z',
834
- deletedAt: null,
835
- createdByActor: null,
836
- updatedByActor: null,
837
- comments: [{
838
- id: 'comment-1',
839
- order: 0,
840
- body: 'Updated review note.',
841
- anchor: { kind: 'document' },
842
- authorActorId: 'user',
843
- authorActor: null,
844
- createdAt: '2026-03-19T12:05:00.000Z',
845
- updatedAt: '2026-03-19T12:12:00.000Z',
846
- deletedAt: null,
847
- }],
848
- },
849
- }),
850
- });
851
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
852
- const originalComment = await screen.findByText('Original review note.');
853
- const commentCard = originalComment.closest('[class*="commentText"]')?.parentElement;
854
- expect(commentCard).not.toBeNull();
855
- await user.click(within(commentCard).getByRole('button', { name: 'Edit' }));
856
- const editor = screen.getByDisplayValue('Original review note.');
857
- await user.clear(editor);
858
- await user.type(editor, 'Updated review note.');
859
- await user.click(screen.getByRole('button', { name: /Save Comment/i }));
860
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions/review-1/comments/comment-1', expect.objectContaining({
861
- method: 'PATCH',
862
- credentials: 'include',
863
- headers: { 'Content-Type': 'application/json' },
864
- }), ''));
865
- expect(await screen.findByText('Updated review note.')).toBeInTheDocument();
866
- });
867
- it('deletes a document review comment from the viewer', async () => {
868
- const user = userEvent.setup();
869
- const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
870
- fetchTaskforceApi
871
- .mockResolvedValueOnce({
872
- ok: true,
873
- text: async () => '# Example\n',
874
- })
875
- .mockResolvedValueOnce({
876
- ok: true,
877
- json: async () => ({
878
- sessions: [{
879
- id: 'review-1',
880
- tenantId: 'default',
881
- workspaceId: 'default',
882
- assetId: 'asset-doc-1',
883
- documentId: 'doc-1',
884
- documentVersion: 1,
885
- title: 'Review 1',
886
- status: 'open',
887
- createdByActorId: 'user',
888
- updatedByActorId: 'user',
889
- createdAt: '2026-03-19T12:00:00.000Z',
890
- updatedAt: '2026-03-19T12:10:00.000Z',
891
- deletedAt: null,
892
- createdByActor: null,
893
- updatedByActor: null,
894
- comments: [{
895
- id: 'comment-1',
896
- order: 0,
897
- body: 'Delete me.',
898
- anchor: { kind: 'document' },
899
- authorActorId: 'user',
900
- authorActor: null,
901
- createdAt: '2026-03-19T12:05:00.000Z',
902
- updatedAt: '2026-03-19T12:05:00.000Z',
903
- deletedAt: null,
904
- }],
905
- }],
906
- }),
907
- })
908
- .mockResolvedValueOnce({
909
- ok: true,
910
- json: async () => ({
911
- success: true,
912
- session: {
913
- id: 'review-1',
914
- tenantId: 'default',
915
- workspaceId: 'default',
916
- assetId: 'asset-doc-1',
917
- documentId: 'doc-1',
918
- documentVersion: 1,
919
- title: 'Review 1',
920
- status: 'open',
921
- createdByActorId: 'user',
922
- updatedByActorId: 'user',
923
- createdAt: '2026-03-19T12:00:00.000Z',
924
- updatedAt: '2026-03-19T12:12:00.000Z',
925
- deletedAt: null,
926
- createdByActor: null,
927
- updatedByActor: null,
928
- comments: [],
929
- },
930
- }),
931
- });
932
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
933
- const commentToDelete = await screen.findByText('Delete me.');
934
- const commentCard = commentToDelete.closest('[class*="commentText"]')?.parentElement;
935
- expect(commentCard).not.toBeNull();
936
- await user.click(within(commentCard).getByRole('button', { name: 'Delete' }));
937
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions/review-1/comments/comment-1', expect.objectContaining({
938
- method: 'DELETE',
939
- credentials: 'include',
940
- }), ''));
941
- await waitFor(() => expect(screen.queryByText('Delete me.')).not.toBeInTheDocument());
942
- confirmSpy.mockRestore();
943
- });
944
- it('uses the resolved cloud api base for document list and save actions in cloud runtime', async () => {
945
- const user = userEvent.setup();
946
- fetchTaskforceApi
947
- .mockResolvedValueOnce({
948
- ok: true,
949
- json: async () => ({ documents: [createDoc()] }),
950
- })
951
- .mockResolvedValueOnce({
952
- ok: true,
953
- text: async () => '# Example\n',
954
- })
955
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
956
- .mockResolvedValueOnce({
957
- ok: true,
958
- json: async () => ({ updatedAt: '2026-03-19T12:00:00.000Z' }),
959
- });
960
- render(_jsx(DocumentWorkspaceShell, { tasks: [], runtimeMode: "cloud", apiBaseUrl: "https://api.taskforce.example", cloudAuthBaseUrl: "https://auth.taskforce.example" }));
961
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(1, '/api/taskforce/documents', expect.objectContaining({ credentials: 'include' }), 'https://api.taskforce.example'));
962
- await screen.findAllByText('Technical Notes');
963
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(2, '/api/taskforce/documents/asset-doc-1/content', expect.objectContaining({ credentials: 'same-origin' }), 'https://api.taskforce.example'));
964
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/document-reviews/sessions?assetId=asset-doc-1', expect.objectContaining({ credentials: 'include' }), 'https://api.taskforce.example'));
965
- await user.click(screen.getByRole('button', { name: /Edit/i }));
966
- const editor = await screen.findByPlaceholderText('Write markdown here…');
967
- await user.clear(editor);
968
- await user.type(editor, '# Updated\n');
969
- await user.click(screen.getByRole('button', { name: /^Save$/i }));
970
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/documents/save', expect.objectContaining({
971
- method: 'POST',
972
- credentials: 'include',
973
- headers: { 'Content-Type': 'application/json' },
974
- }), 'https://api.taskforce.example'));
975
- });
976
- it('uses the forwarded api base url for rename, delete, and task generation actions', async () => {
977
- const user = userEvent.setup();
978
- fetchTaskforceApi
979
- .mockResolvedValueOnce({
980
- ok: true,
981
- text: async () => '# Example\n',
982
- })
983
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
984
- .mockResolvedValueOnce({
985
- ok: true,
986
- json: async () => ({ updatedAt: '2026-03-19T12:00:00.000Z' }),
987
- })
988
- .mockResolvedValueOnce({
989
- ok: true,
990
- json: async () => ({ success: true, softDeleted: true }),
991
- })
992
- .mockResolvedValueOnce({
993
- ok: true,
994
- json: async () => ({
995
- tasks: [{
996
- idempotencyKey: 'task-1',
997
- title: 'Generated task',
998
- description: 'Generated description',
999
- type: 'feature',
1000
- priority: 2,
1001
- children: [],
1002
- selected: true,
1003
- }]
1004
- }),
1005
- })
1006
- .mockResolvedValueOnce({
1007
- ok: true,
1008
- json: async () => ({ created: 1 }),
1009
- });
1010
- const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
1011
- const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => { });
1012
- const doc = createDoc({
1013
- attachmentCount: 0,
1014
- attachmentState: 'unattached',
1015
- });
1016
- const { rerender } = render(_jsx(DocumentViewer, { doc: doc, taskTitle: null, apiBaseUrl: "http://127.0.0.1:4280", enableTaskGeneration: true }));
1017
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(1, '/api/taskforce/documents/asset-doc-1/content', expect.objectContaining({ credentials: 'same-origin' }), 'http://127.0.0.1:4280'));
1018
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(2, '/api/taskforce/document-reviews/sessions?assetId=asset-doc-1', expect.objectContaining({ credentials: 'include' }), 'http://127.0.0.1:4280'));
1019
- await user.dblClick(screen.getByText('Technical Notes'));
1020
- const renameInput = await screen.findByDisplayValue('Technical Notes');
1021
- await user.clear(renameInput);
1022
- await user.type(renameInput, 'Renamed Doc');
1023
- fireEvent.blur(renameInput);
1024
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(3, '/api/taskforce/documents/asset-doc-1', expect.objectContaining({
1025
- method: 'PATCH',
1026
- credentials: 'include',
1027
- headers: { 'Content-Type': 'application/json' },
1028
- }), 'http://127.0.0.1:4280'));
1029
- expect(await screen.findByText('Renamed Doc')).toBeInTheDocument();
1030
- await user.click(screen.getByRole('button', { name: /Delete Permanently/i }));
1031
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/documents/asset-doc-1', expect.objectContaining({
1032
- method: 'DELETE',
1033
- credentials: 'include',
1034
- }), 'http://127.0.0.1:4280'));
1035
- rerender(_jsx(DocumentGenerateModal, { content: "# Plan", docTitle: "Renamed Doc", apiBaseUrl: "http://127.0.0.1:4280", onClose: () => { }, onGenerated: () => { } }));
1036
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(5, '/api/taskforce/documents/generate-tasks/preview', expect.objectContaining({
1037
- method: 'POST',
1038
- credentials: 'include',
1039
- headers: { 'Content-Type': 'application/json' },
1040
- }), 'http://127.0.0.1:4280'));
1041
- await user.click(screen.getByRole('button', { name: /Generate 1 Task/i }));
1042
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(6, '/api/taskforce/documents/generate-tasks/commit', expect.objectContaining({
1043
- method: 'POST',
1044
- credentials: 'include',
1045
- headers: { 'Content-Type': 'application/json' },
1046
- }), 'http://127.0.0.1:4280'));
1047
- confirmSpy.mockRestore();
1048
- alertSpy.mockRestore();
1049
- });
1050
- it('falls back to the readable document name in the viewer when displayName is missing', async () => {
1051
- fetchTaskforceApi
1052
- .mockResolvedValueOnce({
1053
- ok: true,
1054
- text: async () => '# Example\n',
1055
- })
1056
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1057
- render(_jsx(DocumentViewer, { doc: {
1058
- id: 'doc-readable-fallback',
1059
- assetId: 'asset-readable-fallback',
1060
- fsPath: 'workspaces/default/assets/documents/technical-notes.md',
1061
- apiUrl: '/api/taskforce/documents/asset-readable-fallback/content',
1062
- taskId: null,
1063
- displayName: '',
1064
- title: 'Technical Notes',
1065
- docType: 'other',
1066
- sizeBytes: 64,
1067
- updatedAt: '2026-03-19T11:00:00.000Z',
1068
- editable: true,
1069
- attachmentCount: 0,
1070
- attachmentState: 'unattached',
1071
- }, taskTitle: null }));
1072
- expect(await screen.findByText('Technical Notes')).toBeInTheDocument();
1073
- expect(screen.queryByText('asset-readable-fallback')).not.toBeInTheDocument();
1074
- });
1075
- it('syncs editor and preview scrolling in split edit mode', async () => {
1076
- const user = userEvent.setup();
1077
- fetchTaskforceApi
1078
- .mockResolvedValueOnce({
1079
- ok: true,
1080
- text: async () => Array.from({ length: 80 }, (_, index) => `- item ${index + 1}`).join('\n'),
1081
- })
1082
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1083
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
1084
- await screen.findByText('item 1');
1085
- await user.click(screen.getByRole('button', { name: /Edit/i }));
1086
- const editor = await screen.findByPlaceholderText('Write markdown here…');
1087
- const preview = await screen.findByTestId('doc-preview-scroll');
1088
- Object.defineProperty(editor, 'scrollHeight', { configurable: true, value: 1000 });
1089
- Object.defineProperty(editor, 'clientHeight', { configurable: true, value: 200 });
1090
- Object.defineProperty(preview, 'scrollHeight', { configurable: true, value: 1600 });
1091
- Object.defineProperty(preview, 'clientHeight', { configurable: true, value: 400 });
1092
- editor.scrollTop = 400;
1093
- fireEvent.scroll(editor);
1094
- await waitFor(() => expect(preview.scrollTop).toBeCloseTo(600, 4));
1095
- preview.scrollTop = 300;
1096
- fireEvent.scroll(preview);
1097
- await waitFor(() => expect(editor.scrollTop).toBeCloseTo(200, 4));
1098
- });
1099
- it('builds isolated print html without injecting the viewer file name as a second header', () => {
1100
- const html = buildPrintDocumentHtml(`# Implementation Plan
1101
-
1102
- **Goal**
1103
-
1104
- Body copy.
1105
-
1106
- - [x] Done
1107
- - [ ] Todo
1108
- `);
1109
- expect(html).toContain('@page');
1110
- expect(html).toContain('margin: 0.94in 1.02in 1in;');
1111
- expect(html).toContain('font: 12.15pt/1.66 Georgia, "Times New Roman", Times, serif;');
1112
- expect(html).toContain('max-width: 5.95in;');
1113
- expect(html).toContain('.tf-print-document h1');
1114
- expect(html).toContain('font-size: 32pt;');
1115
- expect(html).toContain('max-width: 84%;');
1116
- expect(html).toContain('.tf-print-document h2');
1117
- expect(html).toContain('font-size: 16.5pt;');
1118
- expect(html).toContain('padding-left: 1.1em;');
1119
- expect(html).toContain('background: transparent;');
1120
- expect(html).toContain('.tf-print-section-label');
1121
- expect(html).toContain('font-size: 10.7pt;');
1122
- expect(html).toContain('letter-spacing: 0.12em;');
1123
- expect(html).toContain('text-transform: uppercase;');
1124
- expect(html).toContain('tf-print-document');
1125
- expect(html).toContain('<h1>Implementation Plan</h1>');
1126
- expect(html).toContain('class="tf-print-section-label"');
1127
- expect(html).toContain('>Goal</p>');
1128
- expect(html).toContain('type="checkbox"');
1129
- expect(html).toContain('readOnly=""');
1130
- expect(html).toContain('checked=""');
1131
- expect(html).not.toContain('Technical Notes');
1132
- expect(html).not.toContain('docViewerTitle');
1133
- });
1134
- it('prints through an isolated iframe document and cleans it up after afterprint', async () => {
1135
- const user = userEvent.setup();
1136
- fetchTaskforceApi
1137
- .mockResolvedValueOnce({
1138
- ok: true,
1139
- text: async () => 'Polished print body\n\nSecond paragraph.',
1140
- })
1141
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1142
- const realCreateElement = document.createElement.bind(document);
1143
- const iframe = realCreateElement('iframe');
1144
- const openSpy = vi.fn();
1145
- const writeSpy = vi.fn();
1146
- const closeSpy = vi.fn();
1147
- const removeSpy = vi.spyOn(iframe, 'remove').mockImplementation(() => { });
1148
- const afterprintHandlers = [];
1149
- const iframePrintSpy = vi.fn();
1150
- const iframeFocusSpy = vi.fn();
1151
- const iframeWindow = {
1152
- focus: iframeFocusSpy,
1153
- print: iframePrintSpy,
1154
- requestAnimationFrame: (callback) => {
1155
- callback(0);
1156
- return 1;
1157
- },
1158
- addEventListener: (eventName, handler) => {
1159
- if (eventName === 'afterprint' && typeof handler === 'function')
1160
- afterprintHandlers.push(handler);
1161
- },
1162
- removeEventListener: vi.fn(),
1163
- };
1164
- const iframeDocument = {
1165
- open: openSpy,
1166
- write: writeSpy,
1167
- close: closeSpy,
1168
- fonts: { ready: Promise.resolve() },
1169
- };
1170
- Object.defineProperty(iframe, 'contentWindow', { configurable: true, value: iframeWindow });
1171
- Object.defineProperty(iframe, 'contentDocument', { configurable: true, value: iframeDocument });
1172
- vi.spyOn(document, 'createElement').mockImplementation(((tagName, options) => {
1173
- if (tagName.toLowerCase() === 'iframe')
1174
- return iframe;
1175
- return realCreateElement(tagName, options);
1176
- }));
1177
- render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: "Parent task" }));
1178
- await screen.findByText('Polished print body');
1179
- await user.click(screen.getByRole('button', { name: /Print/i }));
1180
- await waitFor(() => expect(writeSpy).toHaveBeenCalledTimes(1));
1181
- expect(writeSpy.mock.calls[0]?.[0]).toContain('Polished print body');
1182
- expect(writeSpy.mock.calls[0]?.[0]).not.toContain('Technical Notes');
1183
- await waitFor(() => expect(iframePrintSpy).toHaveBeenCalledTimes(1));
1184
- expect(afterprintHandlers).toHaveLength(1);
1185
- afterprintHandlers[0](new Event('afterprint'));
1186
- expect(removeSpy).toHaveBeenCalled();
1187
- });
1188
- it('schedules print cleanup with the expected fallback delay', () => {
1189
- const cleanup = vi.fn();
1190
- const timeoutSpy = vi.spyOn(window, 'setTimeout').mockReturnValue(1);
1191
- schedulePrintCleanup(cleanup);
1192
- expect(timeoutSpy).toHaveBeenCalledWith(cleanup, 1500);
1193
- });
1194
- it('falls back to attachmentCount when attachmentState is missing', async () => {
1195
- const user = userEvent.setup();
1196
- const docs = [
1197
- {
1198
- id: 'doc-attached-fallback',
1199
- assetId: 'doc-attached-fallback',
1200
- fsPath: 'workspaces/default/assets/documents/doc-attached-fallback.md',
1201
- apiUrl: '/api/taskforce/documents/doc-attached-fallback/content',
1202
- taskId: 'task-1',
1203
- displayName: 'Attached fallback',
1204
- title: 'Attached fallback',
1205
- docType: 'other',
1206
- sizeBytes: 128,
1207
- updatedAt: '2026-03-19T10:00:00.000Z',
1208
- editable: true,
1209
- attachmentCount: 1,
1210
- },
1211
- {
1212
- id: 'doc-unattached-fallback',
1213
- assetId: 'doc-unattached-fallback',
1214
- fsPath: 'workspaces/default/assets/documents/doc-unattached-fallback.md',
1215
- apiUrl: '/api/taskforce/documents/doc-unattached-fallback/content',
1216
- taskId: null,
1217
- displayName: 'Unattached fallback',
1218
- title: 'Unattached fallback',
1219
- docType: 'other',
1220
- sizeBytes: 64,
1221
- updatedAt: '2026-03-19T11:00:00.000Z',
1222
- editable: true,
1223
- attachmentCount: 0,
1224
- },
1225
- ];
1226
- const { rerender } = renderDocumentWorkspace({
1227
- tasks: [{ id: 'task-1', title: 'Parent task' }],
1228
- documents: docs.filter((doc) => doc.taskId),
1229
- hasActiveFilters: true,
1230
- });
1231
- expect(screen.getByText('Attached fallback')).toBeInTheDocument();
1232
- expect(screen.queryByText('Unattached fallback')).not.toBeInTheDocument();
1233
- rerender(_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs.filter((doc) => !doc.taskId), loading: false, error: null, selectedDoc: null, searchQuery: "", hasActiveFilters: true, onSearchChange: () => { }, onSelectDoc: () => { }, onRefresh: () => { }, onDeleteSelected: () => { } }));
1234
- expect(screen.getByText('Unattached fallback')).toBeInTheDocument();
1235
- expect(screen.queryByText('Attached fallback')).not.toBeInTheDocument();
1236
- });
1237
- it('switches the active document when a different index item is selected', async () => {
1238
- const user = userEvent.setup();
1239
- const docs = [
1240
- createDoc({
1241
- id: 'doc-first',
1242
- assetId: 'asset-doc-first',
1243
- apiUrl: '/api/taskforce/documents/asset-doc-first/content',
1244
- displayName: 'First notes',
1245
- title: 'First notes',
1246
- }),
1247
- createDoc({
1248
- id: 'doc-second',
1249
- assetId: 'asset-doc-second',
1250
- apiUrl: '/api/taskforce/documents/asset-doc-second/content',
1251
- displayName: 'Second notes',
1252
- title: 'Second notes',
1253
- fsPath: 'workspaces/default/assets/documents/second-notes.md',
1254
- }),
1255
- ];
1256
- fetchTaskforceApi
1257
- .mockResolvedValueOnce({
1258
- ok: true,
1259
- json: async () => ({ documents: docs }),
1260
- })
1261
- .mockResolvedValueOnce({
1262
- ok: true,
1263
- text: async () => '# First content\n',
1264
- })
1265
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
1266
- .mockResolvedValueOnce({
1267
- ok: true,
1268
- text: async () => '# Second content\n',
1269
- })
1270
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1271
- render(_jsx(DocumentWorkspaceShell, { tasks: [], runtimeMode: "local" }));
1272
- expect(await screen.findByText('First content')).toBeInTheDocument();
1273
- await user.click(screen.getByRole('button', { name: /Second notes/i }));
1274
- expect(await screen.findByText('Second content')).toBeInTheDocument();
1275
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/documents/asset-doc-second/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
1276
- });
1277
- it('returns to the remaining filtered document when the active selection is filtered out', async () => {
1278
- const user = userEvent.setup();
1279
- const docs = [
1280
- createDoc({
1281
- id: 'doc-attached-return',
1282
- assetId: 'asset-doc-attached-return',
1283
- apiUrl: '/api/taskforce/documents/asset-doc-attached-return/content',
1284
- displayName: 'Attached return doc',
1285
- title: 'Attached return doc',
1286
- attachmentCount: 1,
1287
- attachmentState: 'attached',
1288
- taskId: 'task-1',
1289
- }),
1290
- createDoc({
1291
- id: 'doc-unattached-return',
1292
- assetId: 'asset-doc-unattached-return',
1293
- apiUrl: '/api/taskforce/documents/asset-doc-unattached-return/content',
1294
- displayName: 'Unattached return doc',
1295
- title: 'Unattached return doc',
1296
- fsPath: 'workspaces/default/assets/documents/unattached-return-doc.md',
1297
- attachmentCount: 0,
1298
- attachmentState: 'unattached',
1299
- taskId: null,
1300
- }),
1301
- ];
1302
- function Harness() {
1303
- const [attachmentFilters, setAttachmentFilters] = useState([]);
1304
- return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => setAttachmentFilters(['attached']), children: "Show attached only" }), _jsx(DocumentWorkspaceShell, { tasks: [{ id: 'task-1', title: 'Parent task' }], runtimeMode: "local", attachmentFilters: attachmentFilters })] }));
1305
- }
1306
- fetchTaskforceApi
1307
- .mockResolvedValueOnce({
1308
- ok: true,
1309
- json: async () => ({ documents: docs }),
1310
- })
1311
- .mockResolvedValueOnce({
1312
- ok: true,
1313
- text: async () => '# Attached return content\n',
1314
- })
1315
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
1316
- .mockResolvedValueOnce({
1317
- ok: true,
1318
- text: async () => '# Unattached return content\n',
1319
- })
1320
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
1321
- .mockResolvedValueOnce({
1322
- ok: true,
1323
- text: async () => '# Attached return content\n',
1324
- })
1325
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1326
- render(_jsx(Harness, {}));
1327
- expect(await screen.findByText('Attached return content')).toBeInTheDocument();
1328
- await user.click(screen.getByRole('button', { name: /Unattached return doc/i }));
1329
- expect(await screen.findByText('Unattached return content')).toBeInTheDocument();
1330
- await user.click(screen.getByRole('button', { name: /Show attached only/i }));
1331
- expect(await screen.findByText('Attached return content')).toBeInTheDocument();
1332
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(6, '/api/taskforce/documents/asset-doc-attached-return/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
1333
- });
1334
- it('selects a requested document by asset id when normalized paths differ', async () => {
1335
- const docs = [
1336
- {
1337
- id: 'asset-doc-unattached',
1338
- assetId: 'asset-doc-unattached',
1339
- fsPath: 'workspaces/default/assets/documents/asset-doc-unattached-random.md',
1340
- apiUrl: '/api/taskforce/documents/asset-doc-unattached/content',
1341
- taskId: null,
1342
- displayName: 'Unattached doc',
1343
- title: 'Unattached doc',
1344
- docType: 'other',
1345
- sizeBytes: 10,
1346
- updatedAt: '2026-03-19T10:00:00.000Z',
1347
- editable: true,
1348
- attachmentCount: 0,
1349
- attachmentState: 'unattached',
1350
- },
1351
- {
1352
- id: 'asset-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
1353
- assetId: 'asset-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
1354
- fsPath: 'workspaces/default/assets/documents/asset-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-notes.md',
1355
- apiUrl: '/api/taskforce/documents/asset-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/content',
1356
- taskId: 'task-1',
1357
- displayName: 'Attached notes',
1358
- title: 'Attached notes',
1359
- docType: 'other',
1360
- sizeBytes: 20,
1361
- updatedAt: '2026-03-19T11:00:00.000Z',
1362
- editable: true,
1363
- attachmentCount: 1,
1364
- attachmentState: 'attached',
1365
- }
1366
- ];
1367
- function Harness() {
1368
- return (_jsx(DocumentWorkspaceShell, { tasks: [{ id: 'task-1', title: 'Parent task' }], runtimeMode: "local", requestedDocPath: "workspaces/default/assets/documents/path-aliases/asset-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }));
1369
- }
1370
- fetchTaskforceApi
1371
- .mockResolvedValueOnce({
1372
- ok: true,
1373
- json: async () => ({ documents: docs }),
1374
- })
1375
- .mockResolvedValueOnce({
1376
- ok: true,
1377
- text: async () => '# Attached notes\n',
1378
- })
1379
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1380
- render(_jsx(Harness, {}));
1381
- await waitFor(() => {
1382
- expect(screen.getByText('Attached notes')).toBeInTheDocument();
1383
- });
1384
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(2, '/api/taskforce/documents/asset-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
1385
- expect(screen.queryByText('Unattached doc')).toBeInTheDocument();
1386
- });
1387
- it('selects a requested document directly by canonical asset id in cloud mode', async () => {
1388
- const docs = [
1389
- {
1390
- id: 'asset-doc-unattached',
1391
- assetId: 'asset-doc-unattached',
1392
- fsPath: 'workspaces/default/assets/documents/asset-doc-unattached-random.md',
1393
- apiUrl: '/api/taskforce/documents/asset-doc-unattached/content',
1394
- taskId: null,
1395
- displayName: 'Unattached doc',
1396
- title: 'Unattached doc',
1397
- docType: 'other',
1398
- sizeBytes: 10,
1399
- updatedAt: '2026-03-19T10:00:00.000Z',
1400
- editable: true,
1401
- attachmentCount: 0,
1402
- attachmentState: 'unattached',
1403
- },
1404
- {
1405
- id: 'asset-doc-cloud-1',
1406
- assetId: 'asset-doc-cloud-1',
1407
- fsPath: 'workspaces/workspace-1/assets/documents/asset-doc-cloud-1-implementation-plan.md',
1408
- apiUrl: '/api/taskforce/documents/asset-doc-cloud-1/content',
1409
- taskId: 'task-1',
1410
- displayName: 'Implementation plan',
1411
- title: 'Implementation plan',
1412
- docType: 'implementation-plan',
1413
- sizeBytes: 20,
1414
- updatedAt: '2026-03-19T11:00:00.000Z',
1415
- editable: true,
1416
- attachmentCount: 1,
1417
- attachmentState: 'attached',
1418
- }
1419
- ];
1420
- function Harness() {
1421
- return (_jsx(DocumentWorkspaceShell, { tasks: [{ id: 'task-1', title: 'Parent task' }], runtimeMode: "cloud", apiBaseUrl: "https://performance-app.taskforcehq.ai", requestedDocAssetId: "asset-doc-cloud-1" }));
1422
- }
1423
- fetchTaskforceApi
1424
- .mockResolvedValueOnce({
1425
- ok: true,
1426
- json: async () => ({ documents: docs }),
1427
- })
1428
- .mockResolvedValueOnce({
1429
- ok: true,
1430
- text: async () => '# Implementation plan\n',
1431
- })
1432
- .mockResolvedValueOnce(createEmptyReviewSessionsResponse());
1433
- render(_jsx(Harness, {}));
1434
- await waitFor(() => {
1435
- expect(screen.getByText('Implementation plan')).toBeInTheDocument();
1436
- });
1437
- await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(2, '/api/taskforce/documents/asset-doc-cloud-1/content', expect.objectContaining({ credentials: 'same-origin' }), 'https://performance-app.taskforcehq.ai'));
1438
- });
1439
- });